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