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