1 /*
   2  * Copyright (c) 2010, 2019, 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 javafx.application.ConditionalFeature;
  29 import javafx.geometry.Dimension2D;
  30 import javafx.scene.image.Image;
  31 import javafx.scene.image.PixelBuffer;
  32 import javafx.scene.input.Dragboard;
  33 import javafx.scene.input.InputMethodRequests;
  34 import javafx.scene.input.KeyCode;
  35 import javafx.scene.input.KeyEvent;
  36 import javafx.scene.input.TransferMode;
  37 import javafx.scene.paint.Color;
  38 import javafx.scene.paint.CycleMethod;
  39 import javafx.scene.paint.ImagePattern;
  40 import javafx.scene.paint.LinearGradient;
  41 import javafx.scene.paint.RadialGradient;
  42 import javafx.scene.paint.Stop;
  43 import javafx.scene.shape.ClosePath;
  44 import javafx.scene.shape.CubicCurveTo;
  45 import javafx.scene.shape.FillRule;
  46 import javafx.scene.shape.LineTo;
  47 import javafx.scene.shape.MoveTo;
  48 import javafx.scene.shape.PathElement;
  49 import javafx.scene.shape.QuadCurveTo;
  50 import javafx.scene.shape.SVGPath;
  51 import javafx.scene.shape.StrokeLineCap;
  52 import javafx.scene.shape.StrokeLineJoin;
  53 import javafx.scene.shape.StrokeType;
  54 import javafx.stage.FileChooser;
  55 import javafx.stage.Modality;
  56 import javafx.stage.StageStyle;
  57 import javafx.stage.Window;
  58 import java.io.File;
  59 import java.io.InputStream;
  60 import java.nio.Buffer;
  61 import java.nio.ByteBuffer;
  62 import java.nio.IntBuffer;
  63 import java.security.AccessControlContext;
  64 import java.security.AccessController;
  65 import java.security.PrivilegedAction;
  66 import java.util.ArrayList;
  67 import java.util.Arrays;
  68 import java.util.Collections;
  69 import java.util.HashMap;
  70 import java.util.List;
  71 import java.util.Map;
  72 import java.util.Set;
  73 import java.util.concurrent.CountDownLatch;
  74 import java.util.concurrent.Future;
  75 import java.util.concurrent.TimeUnit;
  76 import java.util.concurrent.atomic.AtomicBoolean;
  77 import java.util.function.Supplier;
  78 import com.sun.glass.ui.Application;
  79 import com.sun.glass.ui.Clipboard;
  80 import com.sun.glass.ui.ClipboardAssistance;
  81 import com.sun.glass.ui.CommonDialogs;
  82 import com.sun.glass.ui.CommonDialogs.FileChooserResult;
  83 import com.sun.glass.ui.EventLoop;
  84 import com.sun.glass.ui.GlassRobot;
  85 import com.sun.glass.ui.Screen;
  86 import com.sun.glass.ui.Timer;
  87 import com.sun.glass.ui.View;
  88 import com.sun.javafx.PlatformUtil;
  89 import com.sun.javafx.application.PlatformImpl;
  90 import com.sun.javafx.embed.HostInterface;
  91 import com.sun.javafx.geom.Path2D;
  92 import com.sun.javafx.geom.PathIterator;
  93 import com.sun.javafx.geom.Shape;
  94 import com.sun.javafx.geom.transform.BaseTransform;
  95 import com.sun.javafx.perf.PerformanceTracker;
  96 import com.sun.javafx.runtime.async.AbstractRemoteResource;
  97 import com.sun.javafx.runtime.async.AsyncOperationListener;
  98 import com.sun.javafx.scene.text.TextLayoutFactory;
  99 import com.sun.javafx.sg.prism.NGNode;
 100 import com.sun.javafx.tk.AppletWindow;
 101 import com.sun.javafx.tk.CompletionListener;
 102 import com.sun.javafx.tk.FileChooserType;
 103 import com.sun.javafx.tk.FontLoader;
 104 import com.sun.javafx.tk.ImageLoader;
 105 import com.sun.javafx.tk.PlatformImage;
 106 import com.sun.javafx.tk.RenderJob;
 107 import com.sun.javafx.tk.ScreenConfigurationAccessor;
 108 import com.sun.javafx.tk.TKClipboard;
 109 import com.sun.javafx.tk.TKDragGestureListener;
 110 import com.sun.javafx.tk.TKDragSourceListener;
 111 import com.sun.javafx.tk.TKDropTargetListener;
 112 import com.sun.javafx.tk.TKScene;
 113 import com.sun.javafx.tk.TKScreenConfigurationListener;
 114 import com.sun.javafx.tk.TKStage;
 115 import com.sun.javafx.tk.TKSystemMenu;
 116 import com.sun.javafx.tk.Toolkit;
 117 import com.sun.prism.BasicStroke;
 118 import com.sun.prism.Graphics;
 119 import com.sun.prism.GraphicsPipeline;
 120 import com.sun.prism.PixelFormat;
 121 import com.sun.prism.RTTexture;
 122 import com.sun.prism.ResourceFactory;
 123 import com.sun.prism.ResourceFactoryListener;
 124 import com.sun.prism.Texture.WrapMode;
 125 import com.sun.prism.impl.Disposer;
 126 import com.sun.prism.impl.PrismSettings;
 127 import com.sun.scenario.DelayedRunnable;
 128 import com.sun.scenario.animation.AbstractMasterTimer;
 129 import com.sun.scenario.effect.FilterContext;
 130 import com.sun.scenario.effect.Filterable;
 131 import com.sun.scenario.effect.impl.prism.PrFilterContext;
 132 import com.sun.scenario.effect.impl.prism.PrImage;
 133 import com.sun.javafx.logging.PulseLogger;
 134 import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGING_ENABLED;
 135 import com.sun.javafx.scene.input.DragboardHelper;
 136 
 137 public final class QuantumToolkit extends Toolkit {
 138 
 139     public static final boolean verbose =
 140             AccessController.doPrivileged((PrivilegedAction<Boolean>) () -> Boolean.getBoolean("quantum.verbose"));
 141 
 142     public static final boolean pulseDebug =
 143             AccessController.doPrivileged((PrivilegedAction<Boolean>) () -> Boolean.getBoolean("quantum.pulse"));
 144 
 145     private static final boolean multithreaded =
 146             AccessController.doPrivileged((PrivilegedAction<Boolean>) () -> {
 147                 // If it is not specified, or it is true, then it should
 148                 // be true. Otherwise it should be false.
 149                 String value = System.getProperty("quantum.multithreaded");
 150                 if (value == null) return true;
 151                 final boolean result = Boolean.parseBoolean(value);
 152                 if (verbose) {
 153                     System.out.println(result ? "Multi-Threading Enabled" : "Multi-Threading Disabled");
 154                 }
 155                 return result;
 156             });
 157 
 158     private static boolean debug =
 159             AccessController.doPrivileged((PrivilegedAction<Boolean>) () -> Boolean.getBoolean("quantum.debug"));
 160 
 161     private static Integer pulseHZ =
 162             AccessController.doPrivileged((PrivilegedAction<Integer>) () -> Integer.getInteger("javafx.animation.pulse"));
 163 
 164     static final boolean liveResize =
 165             AccessController.doPrivileged((PrivilegedAction<Boolean>) () -> {
 166                 boolean isSWT = "swt".equals(System.getProperty("glass.platform"));
 167                 String result = (PlatformUtil.isMac() || PlatformUtil.isWindows()) && !isSWT ? "true" : "false";
 168                 return "true".equals(System.getProperty("javafx.live.resize", result));
 169             });
 170 
 171     static final boolean drawInPaint =
 172             AccessController.doPrivileged((PrivilegedAction<Boolean>) () -> {
 173                 boolean isSWT = "swt".equals(System.getProperty("glass.platform"));
 174                 String result = PlatformUtil.isMac() && isSWT ? "true" : "false";
 175                 return "true".equals(System.getProperty("javafx.draw.in.paint", result));});
 176 
 177     private static boolean singleThreaded =
 178             AccessController.doPrivileged((PrivilegedAction<Boolean>) () -> {
 179                 Boolean result = Boolean.getBoolean("quantum.singlethreaded");
 180                 if (/*verbose &&*/ result) {
 181                     System.out.println("Warning: Single GUI Threadiong is enabled, FPS should be slower");
 182                 }
 183                 return result;
 184             });
 185 
 186     private static boolean noRenderJobs =
 187             AccessController.doPrivileged((PrivilegedAction<Boolean>) () -> {
 188                 Boolean result = Boolean.getBoolean("quantum.norenderjobs");
 189                 if (/*verbose &&*/ result) {
 190                     System.out.println("Warning: Quantum will not submit render jobs, nothing should draw");
 191                 }
 192                 return result;
 193             });
 194 
 195     private class PulseTask {
 196         private volatile boolean isRunning;
 197         PulseTask(boolean state) {
 198             isRunning = state;
 199         }
 200 
 201         synchronized void set(boolean state) {
 202             isRunning = state;
 203             if (isRunning) {
 204                 resumeTimer();
 205             }
 206         }
 207 
 208         boolean get() {
 209             return isRunning;
 210         }
 211     }
 212 
 213     private AtomicBoolean           toolkitRunning = new AtomicBoolean(false);
 214     private PulseTask               animationRunning = new PulseTask(false);
 215     private PulseTask               nextPulseRequested = new PulseTask(false);
 216     private AtomicBoolean           pulseRunning = new AtomicBoolean(false);
 217     private int                     inPulse = 0;
 218     private CountDownLatch          launchLatch = new CountDownLatch(1);
 219 
 220     final int                       PULSE_INTERVAL = (int)(TimeUnit.SECONDS.toMillis(1L) / getRefreshRate());
 221     final int                       FULLSPEED_INTERVAL = 1;     // ms
 222     boolean                         nativeSystemVsync = false;
 223     private long                    firstPauseRequestTime = 0;
 224     private boolean                 pauseRequested = false;
 225     private static final long       PAUSE_THRESHOLD_DURATION = 250;
 226     private float                   _maxPixelScale;
 227     private Runnable                pulseRunnable, userRunnable, timerRunnable;
 228     private Timer                   pulseTimer = null;
 229     private Thread                  shutdownHook = null;
 230     private PaintCollector          collector;
 231     private QuantumRenderer         renderer;
 232     private GraphicsPipeline        pipeline;
 233 
 234     private ClassLoader             ccl;
 235 
 236     private HashMap<Object,EventLoop> eventLoopMap = null;
 237 
 238     private final PerformanceTracker perfTracker = new PerformanceTrackerImpl();
 239 
 240     @Override public boolean init() {
 241         /*
 242          * Glass Mac, X11 need Application.setDeviceDetails to happen prior to Glass Application.Run
 243          */
 244         renderer = QuantumRenderer.getInstance();
 245         collector = PaintCollector.createInstance(this);
 246         pipeline = GraphicsPipeline.getPipeline();
 247 
 248         /* shutdown the pipeline on System.exit, ^c
 249          * needed with X11 and Windows, see RT-32501
 250          */
 251         shutdownHook = new Thread("Glass/Prism Shutdown Hook") {
 252             @Override public void run() {
 253                 dispose();
 254             }
 255         };
 256         AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 257             Runtime.getRuntime().addShutdownHook(shutdownHook);
 258             return null;
 259         });
 260         return true;
 261     }
 262 
 263     /**
 264      * This method is invoked by PlatformImpl. It is typically called on the main
 265      * thread, NOT the JavaFX Application Thread. The userStartupRunnable will
 266      * be invoked on the JavaFX Application Thread.
 267      *
 268      * @param userStartupRunnable A runnable invoked on the JavaFX Application Thread
 269      *                            that allows the system to perform some startup
 270      *                            functionality after the toolkit has been initialized.
 271      */
 272     @Override public void startup(final Runnable userStartupRunnable) {
 273         // Save the context class loader of the launcher thread
 274         ccl = Thread.currentThread().getContextClassLoader();
 275 
 276         try {
 277             this.userRunnable = userStartupRunnable;
 278 
 279             // Ensure that the toolkit can only be started here
 280             Application.run(() -> runToolkit());
 281         } catch (RuntimeException ex) {
 282             if (verbose) {
 283                 ex.printStackTrace();
 284             }
 285             throw ex;
 286         } catch (Throwable t) {
 287             if (verbose) {
 288                 t.printStackTrace();
 289             }
 290             throw new RuntimeException(t);
 291         }
 292 
 293         try {
 294             launchLatch.await();
 295         } catch (InterruptedException ie) {
 296             ie.printStackTrace();
 297         }
 298     }
 299 
 300     // restart the toolkit if previously terminated
 301     private void assertToolkitRunning() {
 302         // not implemented
 303     }
 304 
 305     boolean shouldWaitForRenderingToComplete() {
 306         return !multithreaded;
 307     }
 308 
 309     /**
 310      * Method to initialize the Scene Graph on the JavaFX application thread.
 311      * Specifically, we will do static initialization for those classes in
 312      * the javafx.stage, javafx.scene, and javafx.controls packages necessary
 313      * to allow subsequent construction of the Scene or any Node, including
 314      * a PopupControl, on a background thread.
 315      *
 316      * This method is called on the JavaFX application thread.
 317      */
 318     private static void initSceneGraph() {
 319         // It is both necessary and sufficient to call a static method on the
 320         // Screen class to allow PopupControl instances to be created on any thread.
 321         javafx.stage.Screen.getPrimary();
 322     }
 323 
 324     // Called by Glass from Application.run()
 325     void runToolkit() {
 326         Thread user = Thread.currentThread();
 327 
 328         if (!toolkitRunning.getAndSet(true)) {
 329             user.setName("JavaFX Application Thread");
 330             // Set context class loader to the same as the thread that called startup
 331             user.setContextClassLoader(ccl);
 332             setFxUserThread(user);
 333 
 334             // Glass screens were inited in Application.run(), assign adapters
 335             assignScreensAdapters();
 336             /*
 337              *  Glass Application instance is now valid - create the ResourceFactory
 338              *  on the render thread
 339              */
 340             renderer.createResourceFactory();
 341 
 342             pulseRunnable = () -> QuantumToolkit.this.pulseFromQueue();
 343             timerRunnable = () -> {
 344                 try {
 345                     QuantumToolkit.this.postPulse();
 346                 } catch (Throwable th) {
 347                     th.printStackTrace(System.err);
 348                 }
 349             };
 350             pulseTimer = Application.GetApplication().createTimer(timerRunnable);
 351 
 352             Application.GetApplication().setEventHandler(new Application.EventHandler() {
 353                 @Override public void handleQuitAction(Application app, long time) {
 354                     GlassStage.requestClosingAllWindows();
 355                 }
 356 
 357                 @Override public boolean handleThemeChanged(String themeName) {
 358                     return PlatformImpl.setAccessibilityTheme(themeName);
 359                 }
 360             });
 361         }
 362         // Initialize JavaFX scene graph
 363         initSceneGraph();
 364         launchLatch.countDown();
 365         try {
 366             Application.invokeAndWait(this.userRunnable);
 367 
 368             if (getMasterTimer().isFullspeed()) {
 369                 /*
 370                  * FULLSPEED_INTVERVAL workaround
 371                  *
 372                  * Application.invokeLater(pulseRunnable);
 373                  */
 374                 pulseTimer.start(FULLSPEED_INTERVAL);
 375             } else {
 376                 nativeSystemVsync = Screen.getVideoRefreshPeriod() != 0.0;
 377                 if (nativeSystemVsync) {
 378                     // system supports vsync
 379                     pulseTimer.start();
 380                 } else {
 381                     // rely on millisecond resolution timer to provide
 382                     // nominal pulse sync and use pulse hinting on
 383                     // synchronous pipelines to fine tune the interval
 384                     pulseTimer.start(PULSE_INTERVAL);
 385                 }
 386             }
 387         } catch (Throwable th) {
 388             th.printStackTrace(System.err);
 389         } finally {
 390             if (PrismSettings.verbose) {
 391                 System.err.println(" vsync: " + PrismSettings.isVsyncEnabled +
 392                                    " vpipe: " + pipeline.isVsyncSupported());
 393             }
 394             PerformanceTracker.logEvent("Toolkit.startup - finished");
 395         }
 396     }
 397 
 398     /**
 399      * Runs the specified supplier, releasing the renderLock if needed.
 400      * This is called by glass event handlers for Window, View, and
 401      * Accessible.
 402      * @param <T> the type of the return value
 403      * @param supplier the supplier to be run
 404      * @return the return value from calling supplier.get()
 405      */
 406     public static <T> T runWithoutRenderLock(Supplier<T> supplier) {
 407         final boolean locked = ViewPainter.renderLock.isHeldByCurrentThread();
 408         try {
 409             if (locked) {
 410                 ViewPainter.renderLock.unlock();
 411             }
 412             return supplier.get();
 413         } finally {
 414             if (locked) {
 415                 ViewPainter.renderLock.lock();
 416             }
 417         }
 418     }
 419 
 420     /**
 421      * Runs the specified supplier, first acquiring the renderLock.
 422      * The lock is released when done.
 423      * @param <T> the type of the return value
 424      * @param supplier the supplier to be run
 425      * @return the return value from calling supplier.get()
 426      */
 427     public static <T> T runWithRenderLock(Supplier<T> supplier) {
 428         ViewPainter.renderLock.lock();
 429         try {
 430             return supplier.get();
 431         } finally {
 432             ViewPainter.renderLock.unlock();
 433         }
 434     }
 435 
 436     boolean hasNativeSystemVsync() {
 437         return nativeSystemVsync;
 438     }
 439 
 440     boolean isVsyncEnabled() {
 441         return (PrismSettings.isVsyncEnabled &&
 442                 pipeline.isVsyncSupported());
 443     }
 444 
 445     @Override public void checkFxUserThread() {
 446         super.checkFxUserThread();
 447         renderer.checkRendererIdle();
 448     }
 449 
 450     protected static Thread getFxUserThread() {
 451         return Toolkit.getFxUserThread();
 452     }
 453 
 454     @Override public Future addRenderJob(RenderJob r) {
 455         // Do not run any render jobs (this is for benchmarking only)
 456         if (noRenderJobs) {
 457             CompletionListener listener = r.getCompletionListener();
 458             if (r instanceof PaintRenderJob) {
 459                 ((PaintRenderJob)r).getScene().setPainting(false);
 460             }
 461             if (listener != null) {
 462                 try {
 463                     listener.done(r);
 464                 } catch (Throwable th) {
 465                     th.printStackTrace();
 466                 }
 467             }
 468             return null;
 469         }
 470         // Run the render job in the UI thread (this is for benchmarking only)
 471         if (singleThreaded) {
 472             r.run();
 473             return null;
 474         }
 475         return (renderer.submitRenderJob(r));
 476     }
 477 
 478     void postPulse() {
 479         if (toolkitRunning.get() &&
 480             (animationRunning.get() || nextPulseRequested.get()) &&
 481             !setPulseRunning()) {
 482 
 483             Application.invokeLater(pulseRunnable);
 484 
 485             if (debug) {
 486                 System.err.println("QT.postPulse@(" + System.nanoTime() + "): " + pulseString());
 487             }
 488         } else if (!animationRunning.get() && !nextPulseRequested.get() && !pulseRunning.get()) {
 489             pauseTimer();
 490         } else if (debug) {
 491             System.err.println("QT.postPulse#(" + System.nanoTime() + "): DROP : " + pulseString());
 492         }
 493     }
 494 
 495     private synchronized void pauseTimer() {
 496         if (!pauseRequested) {
 497             pauseRequested = true;
 498             firstPauseRequestTime = System.currentTimeMillis();
 499         }
 500 
 501         if (System.currentTimeMillis() - firstPauseRequestTime >= PAUSE_THRESHOLD_DURATION) {
 502             pulseTimer.pause();
 503             if (debug) {
 504                 System.err.println("QT.pauseTimer#(" + System.nanoTime() + "): Pausing Timer : " + pulseString());
 505             }
 506         } else if (debug) {
 507             System.err.println("QT.pauseTimer#(" + System.nanoTime() + "): Pause Timer : DROP : " + pulseString());
 508         }
 509     }
 510 
 511     private synchronized void resumeTimer() {
 512         pauseRequested = false;
 513         pulseTimer.resume();
 514     }
 515 
 516     private String pulseString() {
 517         return ((toolkitRunning.get() ? "T" : "t") +
 518                 (animationRunning.get() ? "A" : "a") +
 519                 (pulseRunning.get() ? "P" : "p") +
 520                 (nextPulseRequested.get() ? "N" : "n"));
 521     }
 522 
 523     private boolean setPulseRunning() {
 524         return (pulseRunning.getAndSet(true));
 525     }
 526 
 527     private void endPulseRunning() {
 528         pulseRunning.set(false);
 529         if (debug) {
 530             System.err.println("QT.endPulse: " + System.nanoTime());
 531         }
 532     }
 533 
 534     void pulseFromQueue() {
 535         try {
 536             pulse();
 537         } finally {
 538             endPulseRunning();
 539         }
 540     }
 541 
 542     protected void pulse() {
 543         pulse(true);
 544     }
 545 
 546     void pulse(boolean collect) {
 547         try {
 548             inPulse++;
 549             if (PULSE_LOGGING_ENABLED) {
 550                 PulseLogger.pulseStart();
 551             }
 552 
 553             if (!toolkitRunning.get()) {
 554                 return;
 555             }
 556             nextPulseRequested.set(false);
 557             if (animationRunnable != null) {
 558                 animationRunning.set(true);
 559                 animationRunnable.run();
 560             } else {
 561                 animationRunning.set(false);
 562             }
 563             firePulse();
 564             if (collect) collector.renderAll();
 565         } finally {
 566             inPulse--;
 567             if (PULSE_LOGGING_ENABLED) {
 568                 PulseLogger.pulseEnd();
 569             }
 570         }
 571     }
 572 
 573     void vsyncHint() {
 574         if (isVsyncEnabled()) {
 575             if (debug) {
 576                 System.err.println("QT.vsyncHint: postPulse: " + System.nanoTime());
 577             }
 578             postPulse();
 579         }
 580     }
 581 
 582     @Override  public AppletWindow createAppletWindow(long parent, String serverName) {
 583         GlassAppletWindow parentWindow = new GlassAppletWindow(parent, serverName);
 584         // Make this the parent window for all future Stages
 585         WindowStage.setAppletWindow(parentWindow);
 586         return parentWindow;
 587     }
 588 
 589     @Override public void closeAppletWindow() {
 590         GlassAppletWindow gaw = WindowStage.getAppletWindow();
 591         if (null != gaw) {
 592             gaw.dispose();
 593             WindowStage.setAppletWindow(null);
 594             // any further strong refs will be in the applet itself
 595         }
 596     }
 597 
 598     @Override public TKStage createTKStage(Window peerWindow, boolean securityDialog, StageStyle stageStyle, boolean primary, Modality modality, TKStage owner, boolean rtl, AccessControlContext acc) {
 599         assertToolkitRunning();
 600         WindowStage stage = new WindowStage(peerWindow, securityDialog, stageStyle, modality, owner);
 601         stage.setSecurityContext(acc);
 602         if (primary) {
 603             stage.setIsPrimary();
 604         }
 605         stage.setRTL(rtl);
 606         stage.init(systemMenu);
 607         return stage;
 608     }
 609 
 610     @Override public boolean canStartNestedEventLoop() {
 611         return inPulse == 0;
 612     }
 613 
 614     @Override public Object enterNestedEventLoop(Object key) {
 615         checkFxUserThread();
 616 
 617         if (key == null) {
 618             throw new NullPointerException();
 619         }
 620 
 621         if (!canStartNestedEventLoop()) {
 622             throw new IllegalStateException("Cannot enter nested loop during animation or layout processing");
 623         }
 624 
 625         if (eventLoopMap == null) {
 626             eventLoopMap = new HashMap<>();
 627         }
 628         if (eventLoopMap.containsKey(key)) {
 629             throw new IllegalArgumentException(
 630                     "Key already associated with a running event loop: " + key);
 631         }
 632         EventLoop eventLoop = Application.GetApplication().createEventLoop();
 633         eventLoopMap.put(key, eventLoop);
 634 
 635         Object ret = eventLoop.enter();
 636 
 637         if (!isNestedLoopRunning()) {
 638             notifyLastNestedLoopExited();
 639         }
 640 
 641         return ret;
 642     }
 643 
 644     @Override public void exitNestedEventLoop(Object key, Object rval) {
 645         checkFxUserThread();
 646 
 647         if (key == null) {
 648             throw new NullPointerException();
 649         }
 650         if (eventLoopMap == null || !eventLoopMap.containsKey(key)) {
 651             throw new IllegalArgumentException(
 652                     "Key not associated with a running event loop: " + key);
 653         }
 654         EventLoop eventLoop = eventLoopMap.get(key);
 655         eventLoopMap.remove(key);
 656         eventLoop.leave(rval);
 657     }
 658 
 659     @Override public void exitAllNestedEventLoops() {
 660         checkFxUserThread();
 661         for (EventLoop eventLoop : eventLoopMap.values()) {
 662             eventLoop.leave(null);
 663         }
 664         eventLoopMap.clear();
 665         eventLoopMap = null;
 666     }
 667 
 668     @Override public TKStage createTKPopupStage(Window peerWindow,
 669                                                 StageStyle popupStyle,
 670                                                 TKStage owner,
 671                                                 AccessControlContext acc) {
 672         assertToolkitRunning();
 673         boolean securityDialog = owner instanceof WindowStage ?
 674                 ((WindowStage)owner).isSecurityDialog() : false;
 675         WindowStage stage = new WindowStage(peerWindow, securityDialog, popupStyle, null, owner);
 676         stage.setSecurityContext(acc);
 677         stage.setIsPopup();
 678         stage.init(systemMenu);
 679         return stage;
 680     }
 681 
 682     @Override public TKStage createTKEmbeddedStage(HostInterface host, AccessControlContext acc) {
 683         assertToolkitRunning();
 684         EmbeddedStage stage = new EmbeddedStage(host);
 685         stage.setSecurityContext(acc);
 686         return stage;
 687     }
 688 
 689     private static ScreenConfigurationAccessor screenAccessor =
 690         new ScreenConfigurationAccessor() {
 691             @Override public int getMinX(Object obj) {
 692                return ((Screen)obj).getX();
 693             }
 694             @Override public int getMinY(Object obj) {
 695                 return ((Screen)obj).getY();
 696             }
 697             @Override public int getWidth(Object obj) {
 698                 return ((Screen)obj).getWidth();
 699             }
 700             @Override public int getHeight(Object obj) {
 701                 return ((Screen)obj).getHeight();
 702             }
 703             @Override public int getVisualMinX(Object obj) {
 704                 return ((Screen)obj).getVisibleX();
 705             }
 706             @Override public int getVisualMinY(Object obj) {
 707                 return ((Screen)obj).getVisibleY();
 708             }
 709             @Override public int getVisualWidth(Object obj) {
 710                 return ((Screen)obj).getVisibleWidth();
 711             }
 712             @Override public int getVisualHeight(Object obj) {
 713                 return ((Screen)obj).getVisibleHeight();
 714             }
 715             @Override public float getDPI(Object obj) {
 716                 return ((Screen)obj).getResolutionX();
 717             }
 718             @Override public float getRecommendedOutputScaleX(Object obj) {
 719                 return ((Screen)obj).getRecommendedOutputScaleX();
 720             }
 721             @Override public float getRecommendedOutputScaleY(Object obj) {
 722                 return ((Screen)obj).getRecommendedOutputScaleY();
 723             }
 724         };
 725 
 726     @Override public ScreenConfigurationAccessor
 727                     setScreenConfigurationListener(final TKScreenConfigurationListener listener) {
 728         Screen.setEventHandler(new Screen.EventHandler() {
 729             @Override public void handleSettingsChanged() {
 730                 notifyScreenListener(listener);
 731             }
 732         });
 733         return screenAccessor;
 734     }
 735 
 736     private static void assignScreensAdapters() {
 737         GraphicsPipeline pipeline = GraphicsPipeline.getPipeline();
 738         for (Screen screen : Screen.getScreens()) {
 739             screen.setAdapterOrdinal(pipeline.getAdapterOrdinal(screen));
 740         }
 741     }
 742 
 743     private static void notifyScreenListener(TKScreenConfigurationListener listener) {
 744         assignScreensAdapters();
 745         listener.screenConfigurationChanged();
 746     }
 747 
 748     @Override public Object getPrimaryScreen() {
 749         return Screen.getMainScreen();
 750     }
 751 
 752     @Override public List<?> getScreens() {
 753         return Screen.getScreens();
 754     }
 755 
 756     @Override
 757     public ScreenConfigurationAccessor getScreenConfigurationAccessor() {
 758         return screenAccessor;
 759     }
 760 
 761     @Override
 762     public PerformanceTracker getPerformanceTracker() {
 763         return perfTracker;
 764     }
 765 
 766     @Override
 767     public PerformanceTracker createPerformanceTracker() {
 768         return new PerformanceTrackerImpl();
 769     }
 770 
 771     // Only currently called from the loadImage method below.  We do not
 772     // necessarily know what the worst render scale we will ever see is
 773     // because the user has control over that, but we should be loading
 774     // all dpi variants of an image at all times anyway and then using
 775     // whichever one is needed to respond to a given rendering request
 776     // rather than predetermining which one to use up front.  If we switch
 777     // to making that decision at render time then this method can go away.
 778     private float getMaxRenderScale() {
 779         if (_maxPixelScale == 0) {
 780             for (Object o : getScreens()) {
 781                 _maxPixelScale = Math.max(_maxPixelScale, ((Screen) o).getRecommendedOutputScaleX());
 782                 _maxPixelScale = Math.max(_maxPixelScale, ((Screen) o).getRecommendedOutputScaleY());
 783             }
 784         }
 785         return _maxPixelScale;
 786     }
 787 
 788     @Override public ImageLoader loadImage(String url, double width, double height, boolean preserveRatio, boolean smooth) {
 789         return new PrismImageLoader2(url, width, height, preserveRatio, getMaxRenderScale(), smooth);
 790     }
 791 
 792     @Override public ImageLoader loadImage(InputStream stream, double width, double height,
 793                                            boolean preserveRatio, boolean smooth) {
 794         return new PrismImageLoader2(stream, width, height, preserveRatio, smooth);
 795     }
 796 
 797     @Override public AbstractRemoteResource<? extends ImageLoader> loadImageAsync(
 798             AsyncOperationListener listener, String url,
 799             double width, double height, boolean preserveRatio, boolean smooth) {
 800         return new PrismImageLoader2.AsyncImageLoader(listener, url, width, height, preserveRatio, smooth);
 801     }
 802 
 803     // Note that this method should only be called by PlatformImpl.runLater
 804     // It should not be called directly by other FX code since the underlying
 805     // glass invokeLater method is not thread-safe with respect to toolkit
 806     // shutdown. Calling Platform.runLater *is* thread-safe even when the
 807     // toolkit is shutting down.
 808     @Override public void defer(Runnable runnable) {
 809         if (!toolkitRunning.get()) return;
 810 
 811         Application.invokeLater(runnable);
 812     }
 813 
 814     @Override public void exit() {
 815         // This method must run on the FX application thread
 816         checkFxUserThread();
 817 
 818         // Turn off pulses so no extraneous runnables are submitted
 819         pulseTimer.stop();
 820 
 821         // We need to wait for the last frame to finish so that the renderer
 822         // is not running while we are shutting down glass.
 823         PaintCollector.getInstance().waitForRenderingToComplete();
 824 
 825         notifyShutdownHooks();
 826 
 827         runWithRenderLock(() -> {
 828             //TODO - should update glass scene view state
 829             //TODO - doesn't matter because we are exiting
 830             Application app = Application.GetApplication();
 831             app.terminate();
 832             return null;
 833         });
 834 
 835         dispose();
 836 
 837         super.exit();
 838     }
 839 
 840     public void dispose() {
 841         if (toolkitRunning.compareAndSet(true, false)) {
 842             pulseTimer.stop();
 843             renderer.stopRenderer();
 844 
 845             try {
 846                 AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 847                     Runtime.getRuntime().removeShutdownHook(shutdownHook);
 848                     return null;
 849                 });
 850             } catch (IllegalStateException ignore) {
 851                 // throw when shutdown hook already removed
 852             }
 853         }
 854     }
 855 
 856     @Override public boolean isForwardTraversalKey(KeyEvent e) {
 857         return (e.getCode() == KeyCode.TAB)
 858                    && (e.getEventType() == KeyEvent.KEY_PRESSED)
 859                    && !e.isShiftDown();
 860     }
 861 
 862     @Override public boolean isBackwardTraversalKey(KeyEvent e) {
 863         return (e.getCode() == KeyCode.TAB)
 864                    && (e.getEventType() == KeyEvent.KEY_PRESSED)
 865                    && e.isShiftDown();
 866     }
 867 
 868     private Map<Object, Object> contextMap = Collections.synchronizedMap(new HashMap<>());
 869     @Override public Map<Object, Object> getContextMap() {
 870         return contextMap;
 871     }
 872 
 873     @Override public int getRefreshRate() {
 874         if (pulseHZ == null) {
 875             return 60;
 876         } else {
 877             return pulseHZ;
 878         }
 879     }
 880 
 881     private DelayedRunnable animationRunnable;
 882     @Override public void setAnimationRunnable(DelayedRunnable animationRunnable) {
 883         if (animationRunnable != null) {
 884             animationRunning.set(true);
 885         }
 886         this.animationRunnable = animationRunnable;
 887     }
 888 
 889     @Override public void requestNextPulse() {
 890         nextPulseRequested.set(true);
 891     }
 892 
 893     @Override public void waitFor(Task t) {
 894         if (t.isFinished()) {
 895             return;
 896         }
 897     }
 898 
 899     @Override protected Object createColorPaint(Color color) {
 900         return new com.sun.prism.paint.Color(
 901                 (float)color.getRed(), (float)color.getGreen(),
 902                 (float)color.getBlue(), (float)color.getOpacity());
 903     }
 904 
 905     private com.sun.prism.paint.Color toPrismColor(Color color) {
 906         return (com.sun.prism.paint.Color) Toolkit.getPaintAccessor().getPlatformPaint(color);
 907     }
 908 
 909     private List<com.sun.prism.paint.Stop> convertStops(List<Stop> paintStops) {
 910         List<com.sun.prism.paint.Stop> stops =
 911             new ArrayList<>(paintStops.size());
 912         for (Stop s : paintStops) {
 913             stops.add(new com.sun.prism.paint.Stop(toPrismColor(s.getColor()),
 914                                                    (float) s.getOffset()));
 915         }
 916         return stops;
 917     }
 918 
 919     @Override protected Object createLinearGradientPaint(LinearGradient paint) {
 920         int cmi = com.sun.prism.paint.Gradient.REPEAT;
 921         CycleMethod cycleMethod = paint.getCycleMethod();
 922         if (cycleMethod == CycleMethod.NO_CYCLE) {
 923             cmi = com.sun.prism.paint.Gradient.PAD;
 924         } else if (cycleMethod == CycleMethod.REFLECT) {
 925             cmi = com.sun.prism.paint.Gradient.REFLECT;
 926         }
 927         // TODO: extract colors/offsets and pass them in directly...
 928         List<com.sun.prism.paint.Stop> stops = convertStops(paint.getStops());
 929         return new com.sun.prism.paint.LinearGradient(
 930             (float)paint.getStartX(), (float)paint.getStartY(), (float)paint.getEndX(), (float)paint.getEndY(),
 931             null, paint.isProportional(), cmi, stops);
 932     }
 933 
 934     @Override
 935     protected Object createRadialGradientPaint(RadialGradient paint) {
 936         float cx = (float)paint.getCenterX();
 937         float cy = (float)paint.getCenterY();
 938         float fa = (float)paint.getFocusAngle();
 939         float fd = (float)paint.getFocusDistance();
 940 
 941         int cmi = 0;
 942         if (paint.getCycleMethod() == CycleMethod.NO_CYCLE) {
 943             cmi = com.sun.prism.paint.Gradient.PAD;
 944         } else if (paint.getCycleMethod() == CycleMethod.REFLECT) {
 945             cmi = com.sun.prism.paint.Gradient.REFLECT;
 946         } else {
 947             cmi = com.sun.prism.paint.Gradient.REPEAT;
 948         }
 949 
 950         // TODO: extract colors/offsets and pass them in directly...
 951         List<com.sun.prism.paint.Stop> stops = convertStops(paint.getStops());
 952         return new com.sun.prism.paint.RadialGradient(cx, cy, fa, fd,
 953                 (float)paint.getRadius(), null, paint.isProportional(), cmi, stops);
 954     }
 955 
 956     @Override
 957     protected Object createImagePatternPaint(ImagePattern paint) {
 958         if (paint.getImage() == null) {
 959             return com.sun.prism.paint.Color.TRANSPARENT;
 960         } else {
 961             return new com.sun.prism.paint.ImagePattern(
 962                     (com.sun.prism.Image) Toolkit.getImageAccessor().getPlatformImage(paint.getImage()),
 963                     (float)paint.getX(),
 964                     (float)paint.getY(),
 965                     (float)paint.getWidth(),
 966                     (float)paint.getHeight(),
 967                     paint.isProportional(),
 968                     Toolkit.getPaintAccessor().isMutable(paint));
 969         }
 970     }
 971 
 972     static BasicStroke tmpStroke = new BasicStroke();
 973     private void initStroke(StrokeType pgtype, double strokewidth,
 974                             StrokeLineCap pgcap,
 975                             StrokeLineJoin pgjoin, float miterLimit,
 976                             float[] dashArray, float dashOffset)
 977     {
 978         int type;
 979         if (pgtype == StrokeType.CENTERED) {
 980             type = BasicStroke.TYPE_CENTERED;
 981         } else if (pgtype == StrokeType.INSIDE) {
 982             type = BasicStroke.TYPE_INNER;
 983         } else {
 984             type = BasicStroke.TYPE_OUTER;
 985         }
 986 
 987         int cap;
 988         if (pgcap == StrokeLineCap.BUTT) {
 989             cap = BasicStroke.CAP_BUTT;
 990         } else if (pgcap == StrokeLineCap.SQUARE) {
 991             cap = BasicStroke.CAP_SQUARE;
 992         } else {
 993             cap = BasicStroke.CAP_ROUND;
 994         }
 995 
 996         int join;
 997         if (pgjoin == StrokeLineJoin.BEVEL) {
 998             join = BasicStroke.JOIN_BEVEL;
 999         } else if (pgjoin == StrokeLineJoin.MITER) {
1000             join = BasicStroke.JOIN_MITER;
1001         } else {
1002             join = BasicStroke.JOIN_ROUND;
1003         }
1004 
1005         tmpStroke.set(type, (float) strokewidth, cap, join, miterLimit);
1006         if ((dashArray != null) && (dashArray.length > 0)) {
1007             tmpStroke.set(dashArray, dashOffset);
1008         } else {
1009             tmpStroke.set((float[])null, 0);
1010         }
1011     }
1012 
1013     @Override
1014     public void accumulateStrokeBounds(Shape shape, float bbox[],
1015                                        StrokeType pgtype,
1016                                        double strokewidth,
1017                                        StrokeLineCap pgcap,
1018                                        StrokeLineJoin pgjoin,
1019                                        float miterLimit,
1020                                        BaseTransform tx)
1021     {
1022 
1023         initStroke(pgtype, strokewidth, pgcap, pgjoin, miterLimit, null, 0);
1024         if (tx.isTranslateOrIdentity()) {
1025             tmpStroke.accumulateShapeBounds(bbox, shape, tx);
1026         } else {
1027             Shape.accumulate(bbox, tmpStroke.createStrokedShape(shape), tx);
1028         }
1029     }
1030 
1031     @Override
1032     public boolean strokeContains(Shape shape, double x, double y,
1033                                   StrokeType pgtype,
1034                                   double strokewidth,
1035                                   StrokeLineCap pgcap,
1036                                   StrokeLineJoin pgjoin,
1037                                   float miterLimit)
1038     {
1039         initStroke(pgtype, strokewidth, pgcap, pgjoin, miterLimit, null, 0);
1040         // TODO: The contains testing could be done directly without creating a Shape
1041         return tmpStroke.createStrokedShape(shape).contains((float) x, (float) y);
1042     }
1043 
1044     @Override
1045     public Shape createStrokedShape(Shape shape,
1046                                     StrokeType pgtype,
1047                                     double strokewidth,
1048                                     StrokeLineCap pgcap,
1049                                     StrokeLineJoin pgjoin,
1050                                     float miterLimit,
1051                                     float[] dashArray,
1052                                     float dashOffset) {
1053         initStroke(pgtype, strokewidth, pgcap, pgjoin, miterLimit,
1054                    dashArray, dashOffset);
1055         return tmpStroke.createStrokedShape(shape);
1056     }
1057 
1058     @Override public Dimension2D getBestCursorSize(int preferredWidth, int preferredHeight) {
1059         return CursorUtils.getBestCursorSize(preferredWidth, preferredHeight);
1060     }
1061 
1062     @Override public int getMaximumCursorColors() {
1063         return 2;
1064     }
1065 
1066     @Override public int getKeyCodeForChar(String character) {
1067         return (character.length() == 1)
1068                 ? com.sun.glass.events.KeyEvent.getKeyCodeForChar(
1069                           character.charAt(0))
1070                 : com.sun.glass.events.KeyEvent.VK_UNDEFINED;
1071     }
1072 
1073     @Override public PathElement[] convertShapeToFXPath(Object shape) {
1074         if (shape == null) {
1075             return new PathElement[0];
1076         }
1077         List<PathElement> elements = new ArrayList<>();
1078         // iterate over the shape and turn it into a series of path
1079         // elements
1080         com.sun.javafx.geom.Shape geomShape = (com.sun.javafx.geom.Shape) shape;
1081         PathIterator itr = geomShape.getPathIterator(null);
1082         PathIteratorHelper helper = new PathIteratorHelper(itr);
1083         PathIteratorHelper.Struct struct = new PathIteratorHelper.Struct();
1084 
1085         while (!helper.isDone()) {
1086             // true if WIND_EVEN_ODD, false if WIND_NON_ZERO
1087             boolean windEvenOdd = helper.getWindingRule() == PathIterator.WIND_EVEN_ODD;
1088             int type = helper.currentSegment(struct);
1089             PathElement el;
1090             if (type == PathIterator.SEG_MOVETO) {
1091                 el = new MoveTo(struct.f0, struct.f1);
1092             } else if (type == PathIterator.SEG_LINETO) {
1093                 el = new LineTo(struct.f0, struct.f1);
1094             } else if (type == PathIterator.SEG_QUADTO) {
1095                 el = new QuadCurveTo(
1096                     struct.f0,
1097                     struct.f1,
1098                     struct.f2,
1099                     struct.f3);
1100             } else if (type == PathIterator.SEG_CUBICTO) {
1101                 el = new CubicCurveTo (
1102                     struct.f0,
1103                     struct.f1,
1104                     struct.f2,
1105                     struct.f3,
1106                     struct.f4,
1107                     struct.f5);
1108             } else if (type == PathIterator.SEG_CLOSE) {
1109                 el = new ClosePath();
1110             } else {
1111                 throw new IllegalStateException("Invalid element type: " + type);
1112             }
1113             helper.next();
1114             elements.add(el);
1115         }
1116 
1117         return elements.toArray(new PathElement[elements.size()]);
1118     }
1119 
1120     @Override public Filterable toFilterable(Image img) {
1121         return PrImage.create((com.sun.prism.Image) Toolkit.getImageAccessor().getPlatformImage(img));
1122     }
1123 
1124     @Override public FilterContext getFilterContext(Object config) {
1125         if (config == null || (!(config instanceof com.sun.glass.ui.Screen))) {
1126             return PrFilterContext.getDefaultInstance();
1127         }
1128         Screen screen = (Screen)config;
1129         return PrFilterContext.getInstance(screen);
1130     }
1131 
1132     @Override public AbstractMasterTimer getMasterTimer() {
1133         return MasterTimer.getInstance();
1134     }
1135 
1136     @Override public FontLoader getFontLoader() {
1137         return com.sun.javafx.font.PrismFontLoader.getInstance();
1138     }
1139 
1140     @Override public TextLayoutFactory getTextLayoutFactory() {
1141         return com.sun.javafx.text.PrismTextLayoutFactory.getFactory();
1142     }
1143 
1144     @Override public Object createSVGPathObject(SVGPath svgpath) {
1145         int windingRule = svgpath.getFillRule() == FillRule.NON_ZERO ? PathIterator.WIND_NON_ZERO : PathIterator.WIND_EVEN_ODD;
1146         Path2D path = new Path2D(windingRule);
1147         path.appendSVGPath(svgpath.getContent());
1148         return path;
1149     }
1150 
1151     @Override public Path2D createSVGPath2D(SVGPath svgpath) {
1152         int windingRule = svgpath.getFillRule() == FillRule.NON_ZERO ? PathIterator.WIND_NON_ZERO : PathIterator.WIND_EVEN_ODD;
1153         Path2D path = new Path2D(windingRule);
1154         path.appendSVGPath(svgpath.getContent());
1155         return path;
1156     }
1157 
1158     @Override public boolean imageContains(Object image, float x, float y) {
1159         if (image == null) {
1160             return false;
1161         }
1162 
1163         com.sun.prism.Image pImage = (com.sun.prism.Image)image;
1164         int intX = (int)x + pImage.getMinX();
1165         int intY = (int)y + pImage.getMinY();
1166 
1167         if (pImage.isOpaque()) {
1168             return true;
1169         }
1170 
1171         if (pImage.getPixelFormat() == PixelFormat.INT_ARGB_PRE) {
1172             IntBuffer ib = (IntBuffer) pImage.getPixelBuffer();
1173             int index = intX + intY * pImage.getRowLength();
1174             if (index >= ib.limit()) {
1175                 return false;
1176             } else {
1177                 return (ib.get(index) & 0xff000000) != 0;
1178             }
1179         } else if (pImage.getPixelFormat() == PixelFormat.BYTE_BGRA_PRE) {
1180             ByteBuffer bb = (ByteBuffer) pImage.getPixelBuffer();
1181             int index = intX * pImage.getBytesPerPixelUnit() + intY * pImage.getScanlineStride() + 3;
1182             if (index >= bb.limit()) {
1183                 return false;
1184             } else {
1185                 return (bb.get(index) & 0xff) != 0;
1186             }
1187         } else if (pImage.getPixelFormat() == PixelFormat.BYTE_ALPHA) {
1188             ByteBuffer bb = (ByteBuffer) pImage.getPixelBuffer();
1189             int index = intX * pImage.getBytesPerPixelUnit() + intY * pImage.getScanlineStride();
1190             if (index >= bb.limit()) {
1191                 return false;
1192             } else {
1193                 return (bb.get(index) & 0xff) != 0;
1194             }
1195         }
1196         return true;
1197     }
1198 
1199     @Override
1200     public boolean isNestedLoopRunning() {
1201         return Application.isNestedLoopRunning();
1202     }
1203 
1204     @Override
1205     public boolean isSupported(ConditionalFeature feature) {
1206         switch (feature) {
1207             case SCENE3D:
1208                 return GraphicsPipeline.getPipeline().is3DSupported();
1209             case EFFECT:
1210                 return GraphicsPipeline.getPipeline().isEffectSupported();
1211             case SHAPE_CLIP:
1212                 return true;
1213             case INPUT_METHOD:
1214                 return Application.GetApplication().supportsInputMethods();
1215             case TRANSPARENT_WINDOW:
1216                 return Application.GetApplication().supportsTransparentWindows();
1217             case UNIFIED_WINDOW:
1218                 return Application.GetApplication().supportsUnifiedWindows();
1219             case TWO_LEVEL_FOCUS:
1220                 return Application.GetApplication().hasTwoLevelFocus();
1221             case VIRTUAL_KEYBOARD:
1222                 return Application.GetApplication().hasVirtualKeyboard();
1223             case INPUT_TOUCH:
1224                 return Application.GetApplication().hasTouch();
1225             case INPUT_MULTITOUCH:
1226                 return Application.GetApplication().hasMultiTouch();
1227             case INPUT_POINTER:
1228                 return Application.GetApplication().hasPointer();
1229             default:
1230                 return false;
1231         }
1232     }
1233 
1234     @Override
1235     public boolean isMSAASupported() {
1236         return  GraphicsPipeline.getPipeline().isMSAASupported();
1237     }
1238 
1239     static TransferMode clipboardActionToTransferMode(final int action) {
1240         switch (action) {
1241             case Clipboard.ACTION_NONE:
1242                 return null;
1243             case Clipboard.ACTION_COPY:
1244             //IE drop action for URL copy
1245             case Clipboard.ACTION_COPY | Clipboard.ACTION_REFERENCE:
1246                 return TransferMode.COPY;
1247             case Clipboard.ACTION_MOVE:
1248             //IE drop action for URL move
1249             case Clipboard.ACTION_MOVE | Clipboard.ACTION_REFERENCE:
1250                 return TransferMode.MOVE;
1251             case Clipboard.ACTION_REFERENCE:
1252                 return TransferMode.LINK;
1253             case Clipboard.ACTION_ANY:
1254                 return TransferMode.COPY; // select a reasonable trasnfer mode as workaround until RT-22840
1255         }
1256         return null;
1257     }
1258 
1259     private QuantumClipboard clipboard;
1260     @Override public TKClipboard getSystemClipboard() {
1261         if (clipboard == null) {
1262             clipboard = QuantumClipboard.getClipboardInstance(new ClipboardAssistance(com.sun.glass.ui.Clipboard.SYSTEM));
1263         }
1264         return clipboard;
1265     }
1266 
1267     private GlassSystemMenu systemMenu = new GlassSystemMenu();
1268     @Override public TKSystemMenu getSystemMenu() {
1269         return systemMenu;
1270     }
1271 
1272     @Override public TKClipboard getNamedClipboard(String name) {
1273         return null;
1274     }
1275 
1276     @Override public void startDrag(TKScene scene, Set<TransferMode> tm, TKDragSourceListener l, Dragboard dragboard) {
1277         if (dragboard == null) {
1278             throw new IllegalArgumentException("dragboard should not be null");
1279         }
1280 
1281         GlassScene view = (GlassScene)scene;
1282         view.setTKDragSourceListener(l);
1283 
1284         QuantumClipboard gc = (QuantumClipboard) DragboardHelper.getPeer(dragboard);
1285         gc.setSupportedTransferMode(tm);
1286         gc.flush();
1287 
1288         // flush causes a modal DnD event loop, when we return, close the clipboard
1289         gc.close();
1290     }
1291 
1292     @Override public void enableDrop(TKScene s, TKDropTargetListener l) {
1293 
1294         assert s instanceof GlassScene;
1295 
1296         GlassScene view = (GlassScene)s;
1297         view.setTKDropTargetListener(l);
1298     }
1299 
1300     @Override public void registerDragGestureListener(TKScene s, Set<TransferMode> tm, TKDragGestureListener l) {
1301 
1302         assert s instanceof GlassScene;
1303 
1304         GlassScene view = (GlassScene)s;
1305         view.setTKDragGestureListener(l);
1306     }
1307 
1308     @Override
1309     public void installInputMethodRequests(TKScene scene, InputMethodRequests requests) {
1310 
1311         assert scene instanceof GlassScene;
1312 
1313         GlassScene view = (GlassScene)scene;
1314         view.setInputMethodRequests(requests);
1315     }
1316 
1317     static class QuantumImage implements com.sun.javafx.tk.ImageLoader, ResourceFactoryListener {
1318 
1319         // cache rt here
1320         private com.sun.prism.RTTexture rt;
1321         private com.sun.prism.Image image;
1322         private ResourceFactory rf;
1323 
1324         QuantumImage(com.sun.prism.Image image) {
1325             this.image = image;
1326         }
1327 
1328         QuantumImage(PixelBuffer<Buffer> pixelBuffer) {
1329             switch (pixelBuffer.getPixelFormat().getType()) {
1330                 case INT_ARGB_PRE:
1331                     image = com.sun.prism.Image.fromPixelBufferPreData(PixelFormat.INT_ARGB_PRE,
1332                             pixelBuffer.getBuffer(), pixelBuffer.getWidth(), pixelBuffer.getHeight());
1333                     break;
1334 
1335                 case BYTE_BGRA_PRE:
1336                     image = com.sun.prism.Image.fromPixelBufferPreData(PixelFormat.BYTE_BGRA_PRE,
1337                             pixelBuffer.getBuffer(), pixelBuffer.getWidth(), pixelBuffer.getHeight());
1338                     break;
1339 
1340                 default:
1341                     throw new InternalError("Unsupported PixelFormat: " + pixelBuffer.getPixelFormat().getType());
1342             }
1343         }
1344 
1345         RTTexture getRT(int w, int h, ResourceFactory rfNew) {
1346             boolean rttOk = rt != null && rf == rfNew &&
1347                     rt.getContentWidth() == w && rt.getContentHeight() == h;
1348             if (rttOk) {
1349                 rt.lock();
1350                 if (rt.isSurfaceLost()) {
1351                     rttOk = false;
1352                 }
1353             }
1354 
1355             if (!rttOk) {
1356                 if (rt != null) {
1357                     rt.dispose();
1358                 }
1359                 if (rf != null) {
1360                     rf.removeFactoryListener(this);
1361                     rf = null;
1362                 }
1363                 rt = rfNew.createRTTexture(w, h, WrapMode.CLAMP_TO_ZERO);
1364                 if (rt != null) {
1365                     rf = rfNew;
1366                     rf.addFactoryListener(this);
1367                 }
1368             }
1369 
1370             return rt;
1371         }
1372 
1373         void dispose() {
1374             if (rt != null) {
1375                 rt.dispose();
1376                 rt = null;
1377             }
1378         }
1379 
1380         void setImage(com.sun.prism.Image img) {
1381             image = img;
1382         }
1383 
1384         @Override
1385         public Exception getException() {
1386             return (image == null)
1387                     ? new IllegalStateException("Unitialized image")
1388                     : null;
1389         }
1390         @Override
1391         public int getFrameCount() { return 1; }
1392         @Override
1393         public PlatformImage getFrame(int index) { return image; }
1394         @Override
1395         public int getFrameDelay(int index) { return 0; }
1396         @Override
1397         public int getLoopCount() { return 0; }
1398         @Override
1399         public double getWidth() { return image.getWidth(); }
1400         @Override
1401         public double getHeight() { return image.getHeight(); }
1402         @Override
1403         public void factoryReset() { dispose(); }
1404         @Override
1405         public void factoryReleased() { dispose(); }
1406     }
1407 
1408     @Override public ImageLoader loadPlatformImage(Object platformImage) {
1409         if (platformImage instanceof QuantumImage) {
1410             return (QuantumImage)platformImage;
1411         }
1412 
1413         if (platformImage instanceof com.sun.prism.Image) {
1414             return new QuantumImage((com.sun.prism.Image) platformImage);
1415         }
1416 
1417         if (platformImage instanceof PixelBuffer) {
1418             return new QuantumImage((PixelBuffer<Buffer>) platformImage);
1419         }
1420 
1421         throw new UnsupportedOperationException("unsupported class for loadPlatformImage");
1422     }
1423 
1424     @Override
1425     public PlatformImage createPlatformImage(int w, int h) {
1426         ByteBuffer bytebuf = ByteBuffer.allocate(w * h * 4);
1427         return com.sun.prism.Image.fromByteBgraPreData(bytebuf, w, h);
1428     }
1429 
1430     @Override
1431     public Object renderToImage(ImageRenderingContext p) {
1432         Object saveImage = p.platformImage;
1433         final ImageRenderingContext params = p;
1434         final com.sun.prism.paint.Paint currentPaint = p.platformPaint instanceof com.sun.prism.paint.Paint ?
1435                 (com.sun.prism.paint.Paint)p.platformPaint : null;
1436 
1437         RenderJob re = new RenderJob(new Runnable() {
1438 
1439             private com.sun.prism.paint.Color getClearColor() {
1440                 if (currentPaint == null) {
1441                     return com.sun.prism.paint.Color.WHITE;
1442                 } else if (currentPaint.getType() == com.sun.prism.paint.Paint.Type.COLOR) {
1443                     return (com.sun.prism.paint.Color) currentPaint;
1444                 } else if (currentPaint.isOpaque()) {
1445                     return com.sun.prism.paint.Color.TRANSPARENT;
1446                 } else {
1447                     return com.sun.prism.paint.Color.WHITE;
1448                 }
1449             }
1450 
1451             private void draw(Graphics g, int x, int y, int w, int h) {
1452                 g.setLights(params.lights);
1453                 g.setDepthBuffer(params.depthBuffer);
1454 
1455                 g.clear(getClearColor());
1456                 if (currentPaint != null &&
1457                         currentPaint.getType() != com.sun.prism.paint.Paint.Type.COLOR) {
1458                     g.getRenderTarget().setOpaque(currentPaint.isOpaque());
1459                     g.setPaint(currentPaint);
1460                     g.fillQuad(0, 0, w, h);
1461                 }
1462 
1463                 // Set up transform
1464                 if (x != 0 || y != 0) {
1465                     g.translate(-x, -y);
1466                 }
1467                 if (params.transform != null) {
1468                     g.transform(params.transform);
1469                 }
1470 
1471                 if (params.root != null) {
1472                     if (params.camera != null) {
1473                         g.setCamera(params.camera);
1474                     }
1475                     NGNode ngNode = params.root;
1476                     ngNode.render(g);
1477                 }
1478 
1479             }
1480 
1481             private void renderTile(int x, int xOffset, int y, int yOffset, int w, int h,
1482                                     IntBuffer buffer, ResourceFactory rf, QuantumImage tileImg, QuantumImage targetImg) {
1483                 RTTexture rt = tileImg.getRT(w, h, rf);
1484                 if (rt == null) {
1485                     return;
1486                 }
1487                 Graphics g = rt.createGraphics();
1488                 draw(g, x + xOffset, y + yOffset, w, h);
1489                 int[] pixels = rt.getPixels();
1490                 if (pixels != null) {
1491                     buffer.put(pixels);
1492                 } else {
1493                     rt.readPixels(buffer, rt.getContentX(), rt.getContentY(), w, h);
1494                 }
1495                 //Copy tile's pixels into the target image
1496                 targetImg.image.setPixels(xOffset, yOffset, w, h,
1497                         javafx.scene.image.PixelFormat.getIntArgbPreInstance(), buffer, w);
1498                 rt.unlock();
1499             }
1500 
1501             private void renderWholeImage(int x, int y, int w, int h, ResourceFactory rf, QuantumImage pImage) {
1502                 RTTexture rt = pImage.getRT(w, h, rf);
1503                 if (rt == null) {
1504                     return;
1505                 }
1506                 Graphics g = rt.createGraphics();
1507                 draw(g, x, y, w, h);
1508                 int[] pixels = rt.getPixels();
1509                 if (pixels != null) {
1510                     pImage.setImage(com.sun.prism.Image.fromIntArgbPreData(pixels, w, h));
1511                 } else {
1512                     IntBuffer ib = IntBuffer.allocate(w * h);
1513                     if (rt.readPixels(ib, rt.getContentX(), rt.getContentY(), w, h)) {
1514                         pImage.setImage(com.sun.prism.Image.fromIntArgbPreData(ib, w, h));
1515                     } else {
1516                         pImage.dispose();
1517                         pImage = null;
1518                     }
1519                 }
1520                 rt.unlock();
1521             }
1522 
1523 
1524             private int computeTileSize(int size, int maxSize) {
1525                 // If 'size' divided by either 2 or 3 produce an exact result
1526                 // and is lesser that the specified maxSize, then use this value
1527                 // as the tile size, as this makes the tiling process more efficient.
1528                 for (int n = 1; n <= 3; n++) {
1529                     int optimumSize = size / n;
1530                     if (optimumSize <= maxSize && optimumSize * n == size) {
1531                         return optimumSize;
1532                     }
1533                 }
1534                 return maxSize;
1535             }
1536 
1537             @Override
1538             public void run() {
1539 
1540                 ResourceFactory rf = GraphicsPipeline.getDefaultResourceFactory();
1541 
1542                 if (!rf.isDeviceReady()) {
1543                     return;
1544                 }
1545 
1546                 int x = params.x;
1547                 int y = params.y;
1548                 int w = params.width;
1549                 int h = params.height;
1550 
1551                 if (w <= 0 || h <= 0) {
1552                     return;
1553                 }
1554 
1555                 boolean errored = false;
1556                 // A temp QuantumImage used only as a RTT cache for rendering tiles.
1557                 var tileRttCache = new QuantumImage((com.sun.prism.Image) null);
1558                 try {
1559                     QuantumImage pImage = (params.platformImage instanceof QuantumImage) ?
1560                             (QuantumImage) params.platformImage : new QuantumImage((com.sun.prism.Image) null);
1561 
1562                     int maxTextureSize = rf.getMaximumTextureSize();
1563                     if (h > maxTextureSize || w > maxTextureSize) {
1564                         // The requested size for the screenshot is too big to fit a single texture,
1565                         // so we need to take several snapshot tiles and merge them into pImage
1566                         if (pImage.image == null) {
1567                             pImage.setImage(com.sun.prism.Image.fromIntArgbPreData(IntBuffer.allocate(w * h), w, h));
1568                         }
1569                         // Find out if it is possible to divide up the image in tiles of the same size
1570                         int tileWidth = computeTileSize(w, maxTextureSize);
1571                         int tileHeight = computeTileSize(h, maxTextureSize);
1572                         IntBuffer buffer = IntBuffer.allocate(tileWidth * tileHeight);
1573 
1574                         // M represents the middle set of tiles each with a size of tileW x tileH.
1575                         // R is the right hand column of tiles,
1576                         // B is the bottom row,
1577                         // C is the corner:
1578                         // +-----------+-----------+  .  +-------+
1579                         // |           |           |  .  |       |
1580                         // |     M     |     M     |  .  |   R   |
1581                         // |           |           |  .  |       |
1582                         // +-----------+-----------+  .  +-------+
1583                         // |           |           |  .  |       |
1584                         // |     M     |     M     |  .  |   R   |
1585                         // |           |           |  .  |       |
1586                         // +-----------+-----------+  .  +-------+
1587                         //       .           .        .      .
1588                         // +-----------+-----------+  .  +-------+
1589                         // |     B     |     B     |  .  |   C   |
1590                         // +-----------+-----------+  .  +-------+
1591 
1592                         // Walk through all same-size "M" tiles
1593                         int xOffset = 0;
1594                         int yOffset = 0;
1595                         var mTileWidth = tileWidth;
1596                         var mTileHeight = tileHeight;
1597                         while (mTileWidth == tileWidth && xOffset < w) {
1598                             yOffset = 0;
1599                             mTileHeight = tileHeight;
1600                             while (mTileHeight == tileHeight && yOffset < h) {
1601                                 renderTile(x, xOffset, y, yOffset, mTileWidth, mTileHeight,
1602                                         buffer, rf, tileRttCache, pImage);
1603                                 yOffset += tileHeight;
1604                                 mTileHeight = Math.min(tileHeight, h - yOffset);
1605                             }
1606                             xOffset += tileWidth;
1607                             mTileWidth = Math.min(tileWidth, w - xOffset);
1608                         }
1609                         // Walk through remaining same-width "B" tiles, if any
1610                         int bOffset = 0;
1611                         int bTileHeight = tileHeight;
1612                         while (bTileHeight == tileHeight && bOffset < h) {
1613                             renderTile(x, xOffset, y, bOffset, mTileWidth, bTileHeight, buffer, rf, tileRttCache, pImage);
1614                             bOffset += tileHeight;
1615                             bTileHeight = Math.min(tileHeight, h - bOffset);
1616                         }
1617                         // Walk through remaining same-height "R" tiles, if any
1618                         int rOffset = 0;
1619                         int rTileWidth = tileWidth;
1620                         while (rTileWidth == tileWidth && rOffset < w) {
1621                             renderTile(x, rOffset, y, yOffset, rTileWidth, mTileHeight, buffer, rf, tileRttCache, pImage);
1622                             rOffset += tileWidth;
1623                             rTileWidth = Math.min(tileWidth, w - rOffset);
1624                         }
1625                         // Render corner "C" tile if needed
1626                         if (bOffset > 0 && rOffset > 0) {
1627                             renderTile(x, rOffset, y, bOffset, rTileWidth, bTileHeight, buffer, rf, tileRttCache, pImage);
1628                         }
1629                     }
1630                     else {
1631                         // The requested size for the screenshot fits max texture size,
1632                         // so we can directly render it in the target image.
1633                         renderWholeImage(x, y, w, h, rf, pImage);
1634                     }
1635                     params.platformImage = pImage;
1636                 } catch (Throwable t) {
1637                     errored = true;
1638                     t.printStackTrace(System.err);
1639                 } finally {
1640                     tileRttCache.dispose();
1641                     Disposer.cleanUp();
1642                     rf.getTextureResourcePool().freeDisposalRequestedAndCheckResources(errored);
1643                 }
1644             }
1645         });
1646 
1647         final CountDownLatch latch = new CountDownLatch(1);
1648         re.setCompletionListener(job -> latch.countDown());
1649         addRenderJob(re);
1650 
1651         do {
1652             try {
1653                 latch.await();
1654                 break;
1655             } catch (InterruptedException ex) {
1656                 ex.printStackTrace();
1657             }
1658         } while (true);
1659 
1660         Object image = params.platformImage;
1661         params.platformImage = saveImage;
1662 
1663         return image;
1664     }
1665 
1666     @Override
1667     public FileChooserResult showFileChooser(final TKStage ownerWindow,
1668                                       final String title,
1669                                       final File initialDirectory,
1670                                       final String initialFileName,
1671                                       final FileChooserType fileChooserType,
1672                                       final List<FileChooser.ExtensionFilter>
1673                                               extensionFilters,
1674                                       final FileChooser.ExtensionFilter selectedFilter) {
1675         WindowStage blockedStage = null;
1676         try {
1677             // NOTE: we block the owner of the owner deliberately.
1678             //       The native system blocks the nearest owner itself.
1679             //       Otherwise sheets on Mac are unusable.
1680             blockedStage = blockOwnerStage(ownerWindow);
1681 
1682             return CommonDialogs.showFileChooser(
1683                     (ownerWindow instanceof WindowStage)
1684                             ? ((WindowStage) ownerWindow).getPlatformWindow()
1685                             : null,
1686                     initialDirectory,
1687                     initialFileName,
1688                     title,
1689                     (fileChooserType == FileChooserType.SAVE)
1690                             ? CommonDialogs.Type.SAVE
1691                             : CommonDialogs.Type.OPEN,
1692                     (fileChooserType == FileChooserType.OPEN_MULTIPLE),
1693                     convertExtensionFilters(extensionFilters),
1694                     extensionFilters.indexOf(selectedFilter));
1695         } finally {
1696             if (blockedStage != null) {
1697                 blockedStage.setEnabled(true);
1698             }
1699         }
1700     }
1701 
1702     @Override
1703     public File showDirectoryChooser(final TKStage ownerWindow,
1704                                      final String title,
1705                                      final File initialDirectory) {
1706         WindowStage blockedStage = null;
1707         try {
1708             // NOTE: we block the owner of the owner deliberately.
1709             //       The native system blocks the nearest owner itself.
1710             //       Otherwise sheets on Mac are unusable.
1711             blockedStage = blockOwnerStage(ownerWindow);
1712 
1713             return CommonDialogs.showFolderChooser(
1714                     (ownerWindow instanceof WindowStage)
1715                             ? ((WindowStage) ownerWindow).getPlatformWindow()
1716                             : null,
1717                     initialDirectory, title);
1718         } finally {
1719             if (blockedStage != null) {
1720                 blockedStage.setEnabled(true);
1721             }
1722         }
1723     }
1724 
1725     private WindowStage blockOwnerStage(final TKStage stage) {
1726         if (stage instanceof WindowStage) {
1727             final TKStage ownerStage = ((WindowStage) stage).getOwner();
1728             if (ownerStage instanceof WindowStage) {
1729                 final WindowStage ownerWindowStage = (WindowStage) ownerStage;
1730                 ownerWindowStage.setEnabled(false);
1731                 return ownerWindowStage;
1732             }
1733         }
1734 
1735         return null;
1736     }
1737 
1738     private static List<CommonDialogs.ExtensionFilter>
1739             convertExtensionFilters(final List<FileChooser.ExtensionFilter>
1740                                             extensionFilters) {
1741         final CommonDialogs.ExtensionFilter[] glassExtensionFilters =
1742                 new CommonDialogs.ExtensionFilter[extensionFilters.size()];
1743 
1744         int i = 0;
1745         for (final FileChooser.ExtensionFilter extensionFilter:
1746                  extensionFilters) {
1747             glassExtensionFilters[i++] =
1748                     new CommonDialogs.ExtensionFilter(
1749                             extensionFilter.getDescription(),
1750                             extensionFilter.getExtensions());
1751         }
1752 
1753         return Arrays.asList(glassExtensionFilters);
1754     }
1755 
1756     @Override
1757     public long getMultiClickTime() {
1758         return View.getMultiClickTime();
1759     }
1760 
1761     @Override
1762     public int getMultiClickMaxX() {
1763         return View.getMultiClickMaxX();
1764     }
1765 
1766     @Override
1767     public int getMultiClickMaxY() {
1768         return View.getMultiClickMaxY();
1769     }
1770 
1771     @Override
1772     public String getThemeName() {
1773         return Application.GetApplication().getHighContrastTheme();
1774     }
1775 
1776     @Override
1777     public GlassRobot createRobot() {
1778         return com.sun.glass.ui.Application.GetApplication().createRobot();
1779     }
1780 }