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