1 /*
   2  * Copyright (c) 2010, 2015, 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;
  27 
  28 import javafx.application.ConditionalFeature;
  29 import javafx.beans.property.ReadOnlyObjectProperty;
  30 import javafx.geometry.Dimension2D;
  31 import javafx.scene.Scene;
  32 import javafx.scene.effect.BlurType;
  33 import javafx.scene.image.Image;
  34 import javafx.scene.image.PixelFormat;
  35 import javafx.scene.image.WritableImage;
  36 import javafx.scene.input.Dragboard;
  37 import javafx.scene.input.InputMethodRequests;
  38 import javafx.scene.input.KeyCode;
  39 import javafx.scene.input.KeyEvent;
  40 import javafx.scene.input.TransferMode;
  41 import javafx.scene.paint.Color;
  42 import javafx.scene.paint.ImagePattern;
  43 import javafx.scene.paint.LinearGradient;
  44 import javafx.scene.paint.Paint;
  45 import javafx.scene.paint.RadialGradient;
  46 import javafx.scene.paint.Stop;
  47 import javafx.scene.shape.PathElement;
  48 import javafx.scene.shape.SVGPath;
  49 import javafx.scene.shape.StrokeLineCap;
  50 import javafx.scene.shape.StrokeLineJoin;
  51 import javafx.scene.shape.StrokeType;
  52 import javafx.stage.FileChooser.ExtensionFilter;
  53 import javafx.stage.Modality;
  54 import javafx.stage.Stage;
  55 import javafx.stage.StageStyle;
  56 import javafx.stage.Window;
  57 import java.io.File;
  58 import java.io.IOException;
  59 import java.io.InputStream;
  60 import java.nio.ByteBuffer;
  61 import java.security.AccessControlContext;
  62 import java.security.AccessController;
  63 import java.security.PrivilegedAction;
  64 import java.util.ArrayList;
  65 import java.util.HashSet;
  66 import java.util.Iterator;
  67 import java.util.List;
  68 import java.util.Map;
  69 import java.util.Set;
  70 import java.util.WeakHashMap;
  71 import java.util.concurrent.CountDownLatch;
  72 import java.util.concurrent.Future;
  73 import com.sun.glass.ui.CommonDialogs.FileChooserResult;
  74 import com.sun.glass.utils.NativeLibLoader;
  75 import com.sun.javafx.PlatformUtil;
  76 import com.sun.javafx.beans.event.AbstractNotifyListener;
  77 import com.sun.javafx.embed.HostInterface;
  78 import com.sun.javafx.geom.Path2D;
  79 import com.sun.javafx.geom.transform.BaseTransform;
  80 import com.sun.javafx.jmx.HighlightRegion;
  81 import com.sun.javafx.perf.PerformanceTracker;
  82 import com.sun.javafx.runtime.VersionInfo;
  83 import com.sun.javafx.runtime.async.AsyncOperation;
  84 import com.sun.javafx.runtime.async.AsyncOperationListener;
  85 import com.sun.javafx.scene.SceneHelper;
  86 import com.sun.javafx.scene.text.HitInfo;
  87 import com.sun.javafx.scene.text.TextLayoutFactory;
  88 import com.sun.javafx.sg.prism.NGCamera;
  89 import com.sun.javafx.sg.prism.NGLightBase;
  90 import com.sun.javafx.sg.prism.NGNode;
  91 import com.sun.javafx.util.ModuleHelper;
  92 import com.sun.scenario.DelayedRunnable;
  93 import com.sun.scenario.animation.AbstractMasterTimer;
  94 import com.sun.scenario.effect.AbstractShadow.ShadowMode;
  95 import com.sun.scenario.effect.Color4f;
  96 import com.sun.scenario.effect.FilterContext;
  97 import com.sun.scenario.effect.Filterable;
  98 
  99 
 100 public abstract class Toolkit {
 101     private static String tk;
 102     private static Toolkit TOOLKIT;
 103     private static Thread fxUserThread = null;
 104 
 105     private static final String QUANTUM_TOOLKIT     = "com.sun.javafx.tk.quantum.QuantumToolkit";
 106     private static final String DEFAULT_TOOLKIT     = QUANTUM_TOOLKIT;
 107 
 108     private static final Map gradientMap = new WeakHashMap();
 109 
 110     private static String lookupToolkitClass(String name) {
 111         if ("prism".equalsIgnoreCase(name)) {
 112             return QUANTUM_TOOLKIT;
 113         } else if ("quantum".equalsIgnoreCase(name)) {
 114             return QUANTUM_TOOLKIT;
 115         }
 116         return name;
 117     }
 118 
 119     private static String getDefaultToolkit() {
 120         if (PlatformUtil.isWindows()) {
 121             return DEFAULT_TOOLKIT;
 122         } else if (PlatformUtil.isMac()) {
 123             return DEFAULT_TOOLKIT;
 124         } else if (PlatformUtil.isLinux()) {
 125             return DEFAULT_TOOLKIT;
 126         } else if (PlatformUtil.isIOS()) {
 127             return DEFAULT_TOOLKIT;
 128         } else if (PlatformUtil.isAndroid()) {
 129            return DEFAULT_TOOLKIT;
 130         }
 131 
 132         throw new UnsupportedOperationException(System.getProperty("os.name") + " is not supported");
 133     }
 134 
 135     public static synchronized Toolkit getToolkit() {
 136         if (TOOLKIT != null) {
 137             return TOOLKIT;
 138         }
 139 
 140         final boolean verbose = AccessController.doPrivileged((PrivilegedAction<Boolean>) () -> Boolean.getBoolean("javafx.verbose"));
 141 
 142         // This loading of msvcr120.dll and msvcp120.dll (VS2013) is required when run with Java 8
 143         // since it was build with VS2010 and doesn't include msvcr120.dll in its JRE.
 144         // Note: See README-builds.html on MSVC requirement: VS2013 is required.
 145         if (PlatformUtil.isWindows()) {
 146             AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 147                 try {
 148                     NativeLibLoader.loadLibrary("msvcr120");
 149                 } catch (Throwable t) {
 150                     if (verbose) {
 151                         System.err.println("Error: failed to load msvcr120.dll : " + t);
 152                     }
 153                 }
 154                 try {
 155                     NativeLibLoader.loadLibrary("msvcp120");
 156                 } catch (Throwable t) {
 157                     if (verbose) {
 158                         System.err.println("Error: failed to load msvcp120.dll : " + t);
 159                     }
 160                 }
 161                 return null;
 162             });
 163         }
 164 
 165         AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
 166             // Get the javafx.version and javafx.runtime.version from a preconstructed
 167             // java class, VersionInfo, created at build time.
 168             VersionInfo.setupSystemProperties();
 169             return null;
 170         });
 171 
 172         boolean userSpecifiedToolkit = true;
 173 
 174         // Check a system property to see if there is a specific toolkit to use.
 175         // This is not a doPriviledged check so that applets cannot use this.
 176         String forcedToolkit = null;
 177         try {
 178             forcedToolkit = System.getProperty("javafx.toolkit");
 179         } catch (SecurityException ex) {}
 180 
 181         if (forcedToolkit == null) {
 182             forcedToolkit = tk;
 183         }
 184         if (forcedToolkit == null) {
 185             userSpecifiedToolkit = false;
 186             forcedToolkit = getDefaultToolkit();
 187         }
 188 
 189         if (forcedToolkit.indexOf('.') == -1) {
 190             // Turn a short name into a fully qualified classname
 191             forcedToolkit = lookupToolkitClass(forcedToolkit);
 192         }
 193 
 194         boolean printToolkit = verbose
 195                 || (userSpecifiedToolkit && !forcedToolkit.endsWith("StubToolkit"));
 196 
 197         try {
 198             Class clz = null;
 199 
 200             try {
 201                 // try our priveledged loader first
 202                 final ClassLoader loader = Toolkit.class.getClassLoader();
 203                 clz = Class.forName(forcedToolkit, false, loader);
 204             } catch (ClassNotFoundException e) {
 205                 // fall back and try the application class loader
 206                 final ClassLoader loader = Thread.currentThread().getContextClassLoader();
 207                 clz = Class.forName(forcedToolkit, false, loader);
 208             }
 209 
 210             // Check that clz is a subclass of Toolkit
 211             if (!Toolkit.class.isAssignableFrom(clz)) {
 212                 throw new IllegalArgumentException("Unrecognized FX Toolkit class: "
 213                         + forcedToolkit);
 214             }
 215 
 216             Object thisModule = ModuleHelper.getModule(Toolkit.class);
 217             ModuleHelper.addReads(thisModule, ModuleHelper.getModule(clz));
 218 
 219             TOOLKIT = (Toolkit)clz.newInstance();
 220             if (TOOLKIT.init()) {
 221                 if (printToolkit) {
 222                     System.err.println("JavaFX: using " + forcedToolkit);
 223                 }
 224                 return TOOLKIT;
 225             }
 226             TOOLKIT = null;
 227         } catch (Exception any) {
 228             TOOLKIT = null;
 229             any.printStackTrace();
 230         }
 231 
 232         throw new RuntimeException("No toolkit found");
 233     }
 234 
 235     protected static Thread getFxUserThread() {
 236         return fxUserThread;
 237     }
 238 
 239     protected static void setFxUserThread(Thread t) {
 240         if (fxUserThread != null) {
 241             throw new IllegalStateException("Error: FX User Thread already initialized");
 242         }
 243 
 244         fxUserThread = t;
 245     }
 246 
 247     public void checkFxUserThread() {
 248         // Throw exception if not on FX user thread
 249         if (!isFxUserThread()) {
 250             throw new IllegalStateException("Not on FX application thread; currentThread = "
 251                     + Thread.currentThread().getName());
 252         }
 253     }
 254 
 255     // Toolkit can override this if needed
 256     public boolean isFxUserThread() {
 257         return Thread.currentThread() == fxUserThread;
 258     }
 259 
 260     protected Toolkit() {
 261     }
 262 
 263     public abstract boolean init();
 264 
 265     /**
 266      * Indicates whether or not a nested event loop can be started
 267      * from the current thread in the current state. Note that a nested
 268      * event loop is not allowed outside of an event handler.
 269      *
 270      * @return flag indicating whether a nested event loop can be started.
 271      */
 272     public abstract boolean canStartNestedEventLoop();
 273 
 274     /**
 275      * Enter a nested event loop and block until the corresponding
 276      * exitNestedEventLoop call is made.
 277      * The key passed into this method is used to
 278      * uniquely identify the matched enter/exit pair. This method creates a
 279      * new nested event loop and blocks until the corresponding
 280      * exitNestedEventLoop method is called with the same key.
 281      * The return value of this method will be the {@code rval}
 282      * object supplied to the exitNestedEventLoop method call that unblocks it.
 283      *
 284      * @param key the Object that identifies the nested event loop, which
 285      * must not be null
 286      *
 287      * @throws IllegalArgumentException if the specified key is associated
 288      * with a nested event loop that has not yet returned
 289      *
 290      * @throws NullPointerException if the key is null
 291      *
 292      * @throws IllegalStateException if this method is called on a thread
 293      * other than the FX Application thread
 294      *
 295      * @return the value passed into the corresponding call to exitEventLoop
 296      */
 297     public abstract Object enterNestedEventLoop(Object key);
 298 
 299     /**
 300      * Exit a nested event loop and unblock the caller of the
 301      * corresponding enterNestedEventLoop.
 302      * The key passed into this method is used to
 303      * uniquely identify the matched enter/exit pair. This method causes the
 304      * nested event loop that was previously created with the key to exit and
 305      * return control to the caller. If the specified nested event loop is not
 306      * the inner-most loop then it will not return until all other inner loops
 307      * also exit.
 308      *
 309      * @param key the Object that identifies the nested event loop, which
 310      * must not be null
 311      *
 312      * @param rval an Object that is returned to the caller of the
 313      * corresponding enterNestedEventLoop. This may be null.
 314      *
 315      * @throws IllegalArgumentException if the specified key is not associated
 316      * with an active nested event loop
 317      *
 318      * @throws NullPointerException if the key is null
 319      *
 320      * @throws IllegalStateException if this method is called on a thread
 321      * other than the FX Application thread
 322      */
 323     public abstract void exitNestedEventLoop(Object key, Object rval);
 324 
 325     public abstract boolean isNestedLoopRunning();
 326 
 327     public abstract TKStage createTKStage(Window peerWindow, boolean securityDialog, StageStyle stageStyle, boolean primary, Modality modality, TKStage owner, boolean rtl, AccessControlContext acc);
 328 
 329     public abstract TKStage createTKPopupStage(Window peerWindow, StageStyle popupStyle, TKStage owner, AccessControlContext acc);
 330     public abstract TKStage createTKEmbeddedStage(HostInterface host, AccessControlContext acc);
 331 
 332     /**
 333      * Creates an AppletWindow using the provided window pointer as the parent
 334      * window.
 335      *
 336      * @param parent the native parent which will contain the primary stage
 337      * window(s). Used on Windows/Linux platforms.
 338      *
 339      * @param serverName the name of CARemoteLayerServer which
 340      * will be used to register native layer. Used on Mac platform.
 341      */
 342     public abstract AppletWindow createAppletWindow(long parent, String serverName);
 343 
 344     /**
 345      * Perform cleanup in preparation for applet termination, including closing
 346      * the applet window.
 347      */
 348     public abstract void closeAppletWindow();
 349 
 350     private final Map<TKPulseListener,AccessControlContext> stagePulseListeners =
 351             new WeakHashMap<TKPulseListener,AccessControlContext>();
 352     private final Map<TKPulseListener,AccessControlContext> scenePulseListeners =
 353             new WeakHashMap<TKPulseListener,AccessControlContext>();
 354     private final Map<TKPulseListener,AccessControlContext> postScenePulseListeners =
 355             new WeakHashMap<TKPulseListener,AccessControlContext>();
 356     private final Map<TKListener,AccessControlContext> toolkitListeners =
 357             new WeakHashMap<TKListener,AccessControlContext>();
 358 
 359     // The set of shutdown hooks is strongly held to avoid premature GC.
 360     private final Set<Runnable> shutdownHooks = new HashSet<Runnable>();
 361 
 362     private void runPulse(final TKPulseListener listener,
 363             final AccessControlContext acc) {
 364 
 365         if (acc == null) {
 366             throw new IllegalStateException("Invalid AccessControlContext");
 367         }
 368 
 369         AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 370             listener.pulse();
 371             return null;
 372         }, acc);
 373     }
 374 
 375     public void firePulse() {
 376         // Stages need to be notified of pulses before scenes so the Stage can resized
 377         // and those changes propogated to scene before it gets its pulse to update
 378 
 379         // Copy of listener map
 380         final Map<TKPulseListener,AccessControlContext> stagePulseList =
 381                 new WeakHashMap<TKPulseListener,AccessControlContext>();
 382         final Map<TKPulseListener,AccessControlContext> scenePulseList =
 383                 new WeakHashMap<TKPulseListener,AccessControlContext>();
 384         final Map<TKPulseListener,AccessControlContext> postScenePulseList =
 385                 new WeakHashMap<TKPulseListener,AccessControlContext>();
 386 
 387         synchronized (this) {
 388             stagePulseList.putAll(stagePulseListeners);
 389             scenePulseList.putAll(scenePulseListeners);
 390             postScenePulseList.putAll(postScenePulseListeners);
 391         }
 392         for (Map.Entry<TKPulseListener,AccessControlContext> entry : stagePulseList.entrySet()) {
 393             runPulse(entry.getKey(), entry.getValue());
 394         }
 395         for (Map.Entry<TKPulseListener,AccessControlContext> entry : scenePulseList.entrySet()) {
 396             runPulse(entry.getKey(), entry.getValue());
 397         }
 398         for (Map.Entry<TKPulseListener,AccessControlContext> entry : postScenePulseList.entrySet()) {
 399             runPulse(entry.getKey(), entry.getValue());
 400         }
 401 
 402         if (lastTkPulseListener != null) {
 403             runPulse(lastTkPulseListener, lastTkPulseAcc);
 404         }
 405     }
 406     public void addStageTkPulseListener(TKPulseListener listener) {
 407         if (listener == null) {
 408             return;
 409         }
 410         synchronized (this) {
 411             AccessControlContext acc = AccessController.getContext();
 412             stagePulseListeners.put(listener, acc);
 413         }
 414     }
 415     public void removeStageTkPulseListener(TKPulseListener listener) {
 416         synchronized (this) {
 417             stagePulseListeners.remove(listener);
 418         }
 419     }
 420     public void addSceneTkPulseListener(TKPulseListener listener) {
 421         if (listener == null) {
 422             return;
 423         }
 424         synchronized (this) {
 425             AccessControlContext acc = AccessController.getContext();
 426             scenePulseListeners.put(listener, acc);
 427         }
 428     }
 429     public void removeSceneTkPulseListener(TKPulseListener listener) {
 430         synchronized (this) {
 431             scenePulseListeners.remove(listener);
 432         }
 433     }
 434     public void addPostSceneTkPulseListener(TKPulseListener listener) {
 435         if (listener == null) {
 436             return;
 437         }
 438         synchronized (this) {
 439             AccessControlContext acc = AccessController.getContext();
 440             postScenePulseListeners.put(listener, acc);
 441         }
 442     }
 443     public void removePostSceneTkPulseListener(TKPulseListener listener) {
 444         synchronized (this) {
 445             postScenePulseListeners.remove(listener);
 446         }
 447     }
 448 
 449     public void addTkListener(TKListener listener) {
 450         if (listener == null) {
 451             return;
 452         }
 453         AccessControlContext acc = AccessController.getContext();
 454         toolkitListeners.put(listener, acc);
 455     }
 456 
 457     public void removeTkListener(TKListener listener) {
 458         toolkitListeners.remove(listener);
 459     }
 460 
 461     private TKPulseListener lastTkPulseListener = null;
 462     private AccessControlContext lastTkPulseAcc = null;
 463     public void setLastTkPulseListener(TKPulseListener listener) {
 464         lastTkPulseAcc = AccessController.getContext();
 465         lastTkPulseListener = listener;
 466     }
 467 
 468     public void addShutdownHook(Runnable hook) {
 469         if (hook == null) {
 470             return;
 471         }
 472         synchronized (shutdownHooks) {
 473             shutdownHooks.add(hook);
 474         }
 475     }
 476 
 477     public void removeShutdownHook(Runnable hook) {
 478         synchronized (shutdownHooks) {
 479             shutdownHooks.remove(hook);
 480         }
 481     }
 482 
 483     protected void notifyShutdownHooks() {
 484         List<Runnable> hooks;
 485         synchronized (shutdownHooks) {
 486             hooks = new ArrayList<Runnable>(shutdownHooks);
 487             shutdownHooks.clear();
 488         }
 489 
 490         for (Runnable hook : hooks) {
 491             hook.run();
 492         }
 493     }
 494 
 495     public void notifyWindowListeners(final List<TKStage> windows) {
 496         for (Map.Entry<TKListener,AccessControlContext> entry : toolkitListeners.entrySet()) {
 497             final TKListener listener = entry.getKey();
 498             final AccessControlContext acc = entry.getValue();
 499             if (acc == null) {
 500                 throw new IllegalStateException("Invalid AccessControlContext");
 501             }
 502 
 503             AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 504                 listener.changedTopLevelWindows(windows);
 505                 return null;
 506             }, acc);
 507         }
 508     }
 509 
 510     public void notifyLastNestedLoopExited() {
 511         for (TKListener listener: toolkitListeners.keySet()) {
 512             listener.exitedLastNestedLoop();
 513         }
 514     }
 515 
 516     // notify the pulse timer code that we need the next pulse to happen
 517     // this flag is cleared each cycle so subsequent pulses must be requested
 518     public abstract void requestNextPulse();
 519 
 520     public abstract Future addRenderJob(RenderJob rj);
 521 
 522     public abstract ImageLoader loadImage(String url,
 523                                           int width, int height,
 524                                           boolean preserveRatio,
 525                                           boolean smooth);
 526     public abstract ImageLoader loadImage(InputStream stream,
 527                                           int width, int height,
 528                                           boolean preserveRatio,
 529                                           boolean smooth);
 530     public abstract AsyncOperation loadImageAsync(
 531                                           AsyncOperationListener<? extends ImageLoader> listener,
 532                                           String url,
 533                                           int width, int height,
 534                                           boolean preserveRatio,
 535                                           boolean smooth);
 536 
 537     /*
 538      * The loadPlatformImage method supports the following image types:
 539      *   - an object returned by the renderToImage method
 540      *   - an instance of com.sun.prism.Image (in case of prism)
 541      *   - an instance of an external image object, which can be a BufferedImage
 542      * If JavaFX Image had one more constructor Image(ImageLoader),
 543      * we could introduce a different method for external image loading support.
 544      */
 545 
 546     public abstract ImageLoader loadPlatformImage(Object platformImage);
 547 
 548     public abstract PlatformImage createPlatformImage(int w, int h);
 549 
 550     // Indicates the default state of smooth for ImageView and MediaView
 551     // Subclasses may override this to provide a platform-specific default
 552     public boolean getDefaultImageSmooth() { return true; }
 553 
 554     public abstract void startup(Runnable runnable);
 555     public abstract void defer(Runnable runnable);
 556     public void exit() {
 557         fxUserThread = null;
 558     }
 559 
 560     public abstract Map<Object, Object> getContextMap();
 561     public abstract int getRefreshRate();
 562     public abstract void setAnimationRunnable(DelayedRunnable animationRunnable);
 563     public abstract PerformanceTracker getPerformanceTracker();
 564     public abstract PerformanceTracker createPerformanceTracker();
 565 
 566     //to be used for testing only
 567     public abstract void waitFor(Task t);
 568 
 569     private Object checkSingleColor(List<Stop> stops) {
 570         if (stops.size() == 2) {
 571             Color c = stops.get(0).getColor();
 572             if (c.equals(stops.get(1).getColor())) {
 573                 return Toolkit.getPaintAccessor().getPlatformPaint(c);
 574             }
 575         }
 576         return null;
 577     }
 578 
 579     private Object getPaint(LinearGradient paint) {
 580         Object p = gradientMap.get(paint);
 581         if (p != null) {
 582             return p;
 583         }
 584         p = checkSingleColor(paint.getStops());
 585         if (p == null) {
 586             p = createLinearGradientPaint(paint);
 587         }
 588         gradientMap.put(paint, p);
 589         return p;
 590     }
 591 
 592     private Object getPaint(RadialGradient paint) {
 593         Object p = gradientMap.get(paint);
 594         if (p != null) {
 595             return p;
 596         }
 597         p = checkSingleColor(paint.getStops());
 598         if (p == null) {
 599             p = createRadialGradientPaint(paint);
 600         }
 601         gradientMap.put(paint, p);
 602         return p;
 603     }
 604 
 605     public Object getPaint(Paint paint) {
 606         if (paint instanceof Color) {
 607             return createColorPaint((Color) paint);
 608         }
 609 
 610         if (paint instanceof LinearGradient) {
 611             return getPaint((LinearGradient) paint);
 612         }
 613 
 614         if (paint instanceof RadialGradient) {
 615             return getPaint((RadialGradient) paint);
 616         }
 617 
 618         if (paint instanceof ImagePattern) {
 619             return createImagePatternPaint((ImagePattern) paint);
 620         }
 621 
 622         return null;
 623     }
 624 
 625     protected static final double clampStopOffset(double offset) {
 626         return (offset > 1.0) ? 1.0 :
 627                (offset < 0.0) ? 0.0 : offset;
 628     }
 629 
 630     protected abstract Object createColorPaint(Color paint);
 631     protected abstract Object createLinearGradientPaint(LinearGradient paint);
 632     protected abstract Object createRadialGradientPaint(RadialGradient paint);
 633     protected abstract Object createImagePatternPaint(ImagePattern paint);
 634 
 635     public abstract void
 636         accumulateStrokeBounds(com.sun.javafx.geom.Shape shape,
 637                                float bbox[],
 638                                StrokeType type,
 639                                double strokewidth,
 640                                StrokeLineCap cap,
 641                                StrokeLineJoin join,
 642                                float miterLimit,
 643                                BaseTransform tx);
 644 
 645     public abstract boolean
 646         strokeContains(com.sun.javafx.geom.Shape shape,
 647                        double x, double y,
 648                        StrokeType type,
 649                        double strokewidth,
 650                        StrokeLineCap cap,
 651                        StrokeLineJoin join,
 652                        float miterLimit);
 653 
 654     public abstract com.sun.javafx.geom.Shape
 655         createStrokedShape(com.sun.javafx.geom.Shape shape,
 656                            StrokeType type,
 657                            double strokewidth,
 658                            StrokeLineCap cap,
 659                            StrokeLineJoin join,
 660                            float miterLimit,
 661                            float[] dashArray,
 662                            float dashOffset);
 663 
 664     public abstract int getKeyCodeForChar(String character);
 665     public abstract Dimension2D getBestCursorSize(int preferredWidth, int preferredHeight);
 666     public abstract int getMaximumCursorColors();
 667     public abstract PathElement[] convertShapeToFXPath(Object shape);
 668     public abstract HitInfo convertHitInfoToFX(Object hit);
 669 
 670     public abstract Filterable toFilterable(Image img);
 671     public abstract FilterContext getFilterContext(Object config);
 672 
 673     public abstract boolean isForwardTraversalKey(KeyEvent e);
 674     public abstract boolean isBackwardTraversalKey(KeyEvent e);
 675 
 676     public abstract AbstractMasterTimer getMasterTimer();
 677 
 678     public abstract FontLoader getFontLoader();
 679     public abstract TextLayoutFactory getTextLayoutFactory();
 680 
 681     public abstract Object createSVGPathObject(SVGPath svgpath);
 682     public abstract Path2D createSVGPath2D(SVGPath svgpath);
 683 
 684     /**
 685      * Tests whether the pixel on the given coordinates in the given image
 686      * is non-empty (not fully transparent). Return value is not defined
 687      * for pixels out of the image bounds.
 688      */
 689     public abstract boolean imageContains(Object image, float x, float y);
 690 
 691     public abstract TKClipboard getSystemClipboard();
 692 
 693     public TKClipboard createLocalClipboard() {
 694         return new LocalClipboard();
 695     }
 696 
 697     public abstract TKSystemMenu getSystemMenu();
 698 
 699     public abstract TKClipboard getNamedClipboard(String name);
 700 
 701     public boolean isSupported(ConditionalFeature feature) { return false; }
 702 
 703     public boolean isMSAASupported() { return false; }
 704 
 705     public abstract ScreenConfigurationAccessor setScreenConfigurationListener(TKScreenConfigurationListener listener);
 706 
 707     public abstract Object getPrimaryScreen();
 708 
 709     public abstract List<?> getScreens();
 710 
 711     public abstract ScreenConfigurationAccessor getScreenConfigurationAccessor();
 712 
 713     public abstract void registerDragGestureListener(TKScene s, Set<TransferMode> tm, TKDragGestureListener l);
 714 
 715     /**
 716      * This function is called when a drag originates within a JavaFX application.
 717      * This means that drags that originate in other applications / from the OS
 718      * do not call this function.
 719      * The argument o represents an object used to identify a scene on which
 720      * the drag has started.
 721      */
 722     public abstract void startDrag(TKScene scene, Set<TransferMode> tm, TKDragSourceListener l, Dragboard dragboard);
 723 
 724     // template function which can be implemented by toolkit impls such that they
 725     // can be informed when a drag and drop operation has completed. This allows
 726     // for any cleanup that may need to be done.
 727     public void stopDrag(Dragboard dragboard) {
 728         // no-op
 729     }
 730 
 731     public abstract void enableDrop(TKScene s, TKDropTargetListener l);
 732 
 733     public interface Task {
 734         boolean isFinished();
 735     }
 736 
 737     public Color4f toColor4f(Color color) {
 738         return new Color4f((float)color.getRed(), (float)color.getGreen(), (float)color.getBlue(), (float)color.getOpacity());
 739     }
 740 
 741 
 742     public ShadowMode toShadowMode(BlurType blurType) {
 743         switch (blurType) {
 744             case ONE_PASS_BOX:
 745                 return ShadowMode.ONE_PASS_BOX;
 746             case TWO_PASS_BOX:
 747                 return ShadowMode.TWO_PASS_BOX;
 748             case THREE_PASS_BOX:
 749                 return ShadowMode.THREE_PASS_BOX;
 750             default:
 751                 return ShadowMode.GAUSSIAN;
 752         }
 753     }
 754 
 755     public abstract void installInputMethodRequests(TKScene scene, InputMethodRequests requests);
 756 
 757     /*
 758      * ImageRenderingContext holds the many parameters passed to
 759      * the renderToImage method.
 760      * The use of the parameters is specified by the renderToImage
 761      * method.
 762      * @see #renderToImage
 763      */
 764     public static class ImageRenderingContext {
 765         // Node to be rendered
 766         public NGNode root;
 767 
 768         // Viewport for rendering
 769         public int x;
 770         public int y;
 771         public int width;
 772         public int height;
 773 
 774         // Initial transform for root node
 775         public BaseTransform transform;
 776 
 777         // Rendering parameters either from Scene or SnapShotParams
 778         public boolean depthBuffer;
 779         public Object platformPaint;
 780         public NGCamera camera;
 781         public NGLightBase[] lights;
 782 
 783         // PlatformImage into which to render or null
 784         public Object platformImage;
 785     }
 786 
 787     /*
 788      * This method renders a PG-graph to a platform image object.
 789      * The returned object can be turned into a useable
 790      * scene graph image using the appropriate factor of the
 791      * Image class.
 792      * The scale specified in the params is used to scale the
 793      * entire rendering before any transforms in the nodes are
 794      * applied.
 795      * The width and height specified in the params represent
 796      * the user space dimensions to be rendered.  The returned
 797      * image will be large enough to hold these dimensions
 798      * scaled by the scale parameter.
 799      * The depthBuffer specified in the params is used to determine
 800      * with or without depthBuffer rendering should be performed.
 801      * The root node is the root of a tree of toolkit-specific
 802      * scene graph peer nodes to be rendered and should have
 803      * been previously created by this toolkit.
 804      * The platformPaint specified in the params must be
 805      * generated by the appropriate Toolkit.createPaint method
 806      * and is used to fill the background of the image before
 807      * rendering the scene graph.
 808      * The platformImage specified in the params may be non-null
 809      * and should be a previous return value from this method.
 810      * If it is non-null then it may be reused as the return value
 811      * of this method if it is still valid and large enough to
 812      * hold the requested size.
 813      *
 814      * @param context a ImageRenderingContext instance specifying
 815      *               the various rendering parameters
 816      * @return a platform specific image object
 817      * @see javafx.scene.image.Image#impl_fromPlatformImage
 818      */
 819 
 820     public abstract Object renderToImage(ImageRenderingContext context);
 821 
 822     /**
 823      * Returns the key code for the key which is commonly used on the
 824      * corresponding platform as a modifier key in shortcuts. For example
 825      * it is {@code KeyCode.CONTROL} on Windows (Ctrl + C, Ctrl + V ...) and
 826      * {@code KeyCode.META} on MacOS (Cmd + C, Cmd + V ...).
 827      *
 828      * @return the key code for shortcut modifier key
 829      */
 830     public KeyCode getPlatformShortcutKey() {
 831         return PlatformUtil.isMac() ? KeyCode.META : KeyCode.CONTROL;
 832     }
 833 
 834     public abstract FileChooserResult showFileChooser(
 835             TKStage ownerWindow,
 836             String title,
 837             File initialDirectory,
 838             String initialFileName,
 839             FileChooserType fileChooserType,
 840             List<ExtensionFilter> extensionFilters,
 841             ExtensionFilter selectedFilter);
 842 
 843     public abstract File showDirectoryChooser(
 844             TKStage ownerWindow,
 845             String title,
 846             File initialDirectory);
 847 
 848     /*
 849      * Methods for obtaining "double-click" speed value.
 850      */
 851     public abstract long getMultiClickTime();
 852     public abstract int getMultiClickMaxX();
 853     public abstract int getMultiClickMaxY();
 854 
 855     private CountDownLatch pauseScenesLatch = null;
 856 
 857     /*
 858      * Causes all scenes to stop by removing its TKPulseListener from Toolkit.
 859      * It is used by Scenegraph-JMX bean.
 860      */
 861     public void pauseScenes() {
 862         pauseScenesLatch = new CountDownLatch(1);
 863         Window.getWindows().stream().forEach(window -> {
 864             final Scene scene = window.getScene();
 865             if (scene != null) {
 866                 this.removeSceneTkPulseListener(scene.impl_getScenePulseListener());
 867             }
 868         });
 869         this.getMasterTimer().pause();
 870         SceneHelper.setPaused(true);
 871     }
 872 
 873     /*
 874      * Resume all scenes by registering its TKPulseListener back to Toolkit.
 875      * It is used by Scenegraph-JMX bean.
 876      */
 877     public void resumeScenes() {
 878         SceneHelper.setPaused(false);
 879         this.getMasterTimer().resume();
 880         Window.getWindows().stream().forEach(window -> {
 881             final Scene scene = window.getScene();
 882             if (scene != null) {
 883                 this.addSceneTkPulseListener(scene.impl_getScenePulseListener());
 884             }
 885         });
 886         pauseScenesLatch.countDown();
 887         pauseScenesLatch = null;
 888     }
 889 
 890     /**
 891      * Used to pause current thread when the Scene-graph is in "PAUSED" mode.
 892      * It is mainly used by {@link javafx.application.Platform#runLater(Runnable)}
 893      * to block the thread and not to pass the runnable to FX event queue.
 894      */
 895     public void pauseCurrentThread() {
 896         final CountDownLatch cdl = pauseScenesLatch;
 897         if (cdl == null) {
 898             return;
 899         }
 900         try {
 901             cdl.await();
 902         } catch (InterruptedException e) { }
 903     }
 904 
 905     private Set<HighlightRegion> highlightRegions;
 906 
 907     /**
 908      * Getter for the set of regions to be highlighted using the JMX tooling
 909      * interface.
 910      *
 911      * @return the set of regions to be highlighted.
 912      */
 913     public Set<HighlightRegion> getHighlightedRegions() {
 914         if (highlightRegions == null) {
 915             highlightRegions = new HashSet<HighlightRegion>();
 916         }
 917         return highlightRegions;
 918     }
 919 
 920     public interface WritableImageAccessor {
 921         public void loadTkImage(WritableImage wimg, Object loader);
 922         public Object getTkImageLoader(WritableImage wimg);
 923     }
 924 
 925     private static WritableImageAccessor writableImageAccessor = null;
 926 
 927     public static void setWritableImageAccessor(WritableImageAccessor accessor) {
 928         writableImageAccessor = accessor;
 929     }
 930 
 931     public static WritableImageAccessor getWritableImageAccessor() {
 932         return writableImageAccessor;
 933     }
 934 
 935     public interface PaintAccessor {
 936         public boolean isMutable(Paint paint);
 937         public Object getPlatformPaint(Paint paint);
 938         public void addListener(Paint paint, AbstractNotifyListener platformChangeListener);
 939         public void removeListener(Paint paint, AbstractNotifyListener platformChangeListener);
 940     }
 941 
 942     private static PaintAccessor paintAccessor = null;
 943 
 944     public static void setPaintAccessor(PaintAccessor accessor) {
 945         paintAccessor = accessor;
 946     }
 947 
 948     public static PaintAccessor getPaintAccessor() {
 949         return paintAccessor;
 950     }
 951 
 952     public interface ImageAccessor {
 953         public boolean isAnimation(Image image);
 954         public ReadOnlyObjectProperty<PlatformImage>getImageProperty(Image image);
 955         public int[] getPreColors(PixelFormat<ByteBuffer> pf);
 956         public int[] getNonPreColors(PixelFormat<ByteBuffer> pf);
 957     }
 958 
 959     private static ImageAccessor imageAccessor = null;
 960 
 961     public static void setImageAccessor(ImageAccessor accessor) {
 962         imageAccessor = accessor;
 963     }
 964 
 965     public static ImageAccessor getImageAccessor() {
 966         return imageAccessor;
 967     }
 968 
 969     public String getThemeName() {
 970         return null;
 971     }
 972 }