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