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