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