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