1 /*
   2  * Copyright (c) 2011, 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 /*
  27  * StubToolkit.java
  28  */
  29 
  30 package com.sun.javafx.pgstub;
  31 
  32 import com.sun.glass.ui.CommonDialogs.FileChooserResult;
  33 import com.sun.javafx.application.PlatformImpl;
  34 import com.sun.javafx.embed.HostInterface;
  35 import com.sun.javafx.geom.Path2D;
  36 import com.sun.javafx.geom.Shape;
  37 import com.sun.javafx.geom.transform.BaseTransform;
  38 import com.sun.javafx.menu.MenuBase;
  39 import com.sun.javafx.perf.PerformanceTracker;
  40 import com.sun.javafx.runtime.async.AsyncOperation;
  41 import com.sun.javafx.runtime.async.AsyncOperationListener;
  42 import com.sun.javafx.scene.text.HitInfo;
  43 import com.sun.javafx.scene.text.TextLayoutFactory;
  44 import com.sun.javafx.tk.*;
  45 import com.sun.prism.BasicStroke;
  46 import com.sun.scenario.DelayedRunnable;
  47 import com.sun.scenario.animation.AbstractMasterTimer;
  48 import com.sun.scenario.effect.FilterContext;
  49 import com.sun.scenario.effect.Filterable;
  50 import javafx.application.ConditionalFeature;
  51 import javafx.geometry.Dimension2D;
  52 import javafx.scene.image.Image;
  53 import javafx.scene.input.*;
  54 import javafx.scene.paint.Color;
  55 import javafx.scene.paint.ImagePattern;
  56 import javafx.scene.paint.LinearGradient;
  57 import javafx.scene.paint.RadialGradient;
  58 import javafx.scene.shape.*;
  59 import javafx.stage.FileChooser.ExtensionFilter;
  60 import javafx.stage.Modality;
  61 import javafx.stage.StageStyle;
  62 import javafx.stage.Window;
  63 import javafx.util.Pair;
  64 
  65 import java.io.File;
  66 import java.io.InputStream;
  67 import java.security.AccessControlContext;
  68 import java.util.*;
  69 import java.util.concurrent.Future;
  70 
  71 /**
  72  * A Toolkit implementation for use with Testing.
  73  *
  74  * @author Richard
  75  */
  76 public class StubToolkit extends Toolkit {
  77 
  78     private Map<Object, Object> contextMap = new HashMap<Object, Object>();
  79 
  80     private StubMasterTimer masterTimer = new StubMasterTimer();
  81 
  82     private PerformanceTracker performanceTracker = new StubPerformanceTracker();
  83 
  84     private final StubImageLoaderFactory imageLoaderFactory =
  85             new StubImageLoaderFactory();
  86 
  87     private CursorSizeConverter cursorSizeConverter =
  88             CursorSizeConverter.NO_CURSOR_SUPPORT;
  89 
  90     private int maximumCursorColors = 2;
  91 
  92     private TKScreenConfigurationListener screenConfigurationListener;
  93 
  94     private static final ScreenConfiguration[] DEFAULT_SCREEN_CONFIG = {
  95                 new ScreenConfiguration(0, 0, 1920, 1200, 0, 0, 1920, 1172, 96)
  96             };
  97 
  98     private ScreenConfiguration[] screenConfigurations = DEFAULT_SCREEN_CONFIG;
  99 
 100     static {
 101         try {
 102             // ugly hack to initialize "runLater" method in Platform.java
 103             PlatformImpl.startup(() -> {});
 104         } catch (Exception ex) {}
 105 
 106         // allow tests to access PG scenegraph
 107         // so that they can run with assertion enabled
 108         javafx.scene.Scene.impl_setAllowPGAccess(true);
 109     }
 110     private boolean pulseRequested;
 111 
 112     /*
 113      * overrides of Toolkit's abstract functions
 114      */
 115 
 116     @Override
 117     public boolean init() {
 118         return true;
 119     }
 120 
 121     @Override
 122     public TKStage createTKStage(Window peerWindow, boolean securityDialog, StageStyle stageStyle, boolean primary, Modality modality, TKStage owner, boolean rtl, AccessControlContext acc) {
 123 
 124         return new StubStage();
 125     }
 126 
 127     @Override
 128     public TKStage createTKPopupStage(Window peerWindow, StageStyle popupStyle, TKStage owner, AccessControlContext acc) {
 129         return new StubPopupStage();
 130     }
 131 
 132     @Override
 133     public TKStage createTKEmbeddedStage(HostInterface host, AccessControlContext acc) {
 134         return new StubStage();
 135     }
 136 
 137     @Override
 138     public AppletWindow createAppletWindow(long parent, String serverName) {
 139         // unsupported
 140         return null;
 141     }
 142 
 143     @Override
 144     public void closeAppletWindow() {
 145         // unsupported
 146     }
 147 
 148     private final TKSystemMenu systemMenu = new StubSystemMenu();
 149     @Override
 150     public TKSystemMenu getSystemMenu() {
 151         return systemMenu;
 152     }
 153 
 154     @Override
 155     public void startup(Runnable runnable) {
 156         runnable.run();
 157     }
 158 
 159     @Override
 160     public void checkFxUserThread() {
 161         // Do nothing
 162     }
 163 
 164     @Override
 165     public boolean isFxUserThread() {
 166         // Always on the FX app thread
 167         return true;
 168     }
 169 
 170     @Override
 171     public void defer(Runnable runnable) {
 172         runnable.run();
 173     }
 174 
 175     @Override
 176     public void exit() {
 177         System.exit(0);
 178     }
 179 
 180     @Override
 181     public Future addRenderJob(RenderJob rj) {
 182         return rj;
 183     }
 184 
 185     @Override
 186     public Map<Object, Object> getContextMap() {
 187         return contextMap;
 188     }
 189 
 190     @Override public int getRefreshRate() {
 191         return -1;
 192     }
 193 
 194     private DelayedRunnable animationRunnable;
 195 
 196     @Override
 197     public void setAnimationRunnable(DelayedRunnable animationRunnable) {
 198         this.animationRunnable = animationRunnable;
 199     }
 200 
 201     @Override
 202     public PerformanceTracker getPerformanceTracker() {
 203         return performanceTracker;
 204     }
 205 
 206     @Override public PerformanceTracker createPerformanceTracker() {
 207         return new StubPerformanceTracker();
 208     }
 209 
 210     @Override
 211     protected Object createColorPaint(Color paint) {
 212         return new com.sun.prism.paint.Color((float) paint.getRed(),
 213                     (float) paint.getGreen(),
 214                     (float) paint.getBlue(),
 215                     (float) paint.getOpacity());
 216     }
 217 
 218     @Override
 219     protected Object createLinearGradientPaint(LinearGradient paint) {
 220         // Non functioning but compiles
 221         return new com.sun.prism.paint.Color(1, 1, 1, 1);
 222     }
 223 
 224     @Override
 225     protected Object createRadialGradientPaint(RadialGradient paint) {
 226         // Non functioning but compiles
 227         return new com.sun.prism.paint.Color(1, 1, 1, 1);
 228     }
 229 
 230     @Override
 231     protected Object createImagePatternPaint(ImagePattern paint) {
 232         // Non functioning but compiles
 233         return new com.sun.prism.paint.Color(1, 1, 1, 1);
 234     }
 235 
 236     static BasicStroke tmpStroke = new BasicStroke();
 237     void initStroke(StrokeType pgtype, double strokewidth,
 238                     StrokeLineCap pgcap,
 239                     StrokeLineJoin pgjoin, float miterLimit)
 240     {
 241         int type;
 242         if (pgtype == StrokeType.CENTERED) {
 243             type = BasicStroke.TYPE_CENTERED;
 244         } else if (pgtype == StrokeType.INSIDE) {
 245             type = BasicStroke.TYPE_INNER;
 246         } else {
 247             type = BasicStroke.TYPE_OUTER;
 248         }
 249 
 250         int cap;
 251         if (pgcap == StrokeLineCap.BUTT) {
 252             cap = BasicStroke.CAP_BUTT;
 253         } else if (pgcap == StrokeLineCap.SQUARE) {
 254             cap = BasicStroke.CAP_SQUARE;
 255         } else {
 256             cap = BasicStroke.CAP_ROUND;
 257         }
 258 
 259         int join;
 260         if (pgjoin == StrokeLineJoin.BEVEL) {
 261             join = BasicStroke.JOIN_BEVEL;
 262         } else if (pgjoin == StrokeLineJoin.MITER) {
 263             join = BasicStroke.JOIN_MITER;
 264         } else {
 265             join = BasicStroke.JOIN_ROUND;
 266         }
 267 
 268         tmpStroke.set(type, (float) strokewidth, cap, join, miterLimit);
 269     }
 270 
 271     @Override
 272     public void accumulateStrokeBounds(Shape shape, float bbox[],
 273                                        StrokeType pgtype,
 274                                        double strokewidth,
 275                                        StrokeLineCap pgcap,
 276                                        StrokeLineJoin pgjoin,
 277                                        float miterLimit,
 278                                        BaseTransform tx)
 279     {
 280 
 281         initStroke(pgtype, strokewidth, pgcap, pgjoin, miterLimit);
 282         // TODO: The accumulation could be done directly without creating a Shape
 283         Shape.accumulate(bbox, tmpStroke.createStrokedShape(shape), tx);
 284     }
 285 
 286     @Override
 287     public boolean strokeContains(Shape shape, double x, double y,
 288                                   StrokeType pgtype,
 289                                   double strokewidth,
 290                                   StrokeLineCap pgcap,
 291                                   StrokeLineJoin pgjoin,
 292                                   float miterLimit)
 293     {
 294         initStroke(pgtype, strokewidth, pgcap, pgjoin, miterLimit);
 295         // TODO: The contains testing could be done directly without creating a Shape
 296         return tmpStroke.createStrokedShape(shape).contains((float) x, (float) y);
 297     }
 298 
 299     @Override
 300     public Shape createStrokedShape(Shape shape,
 301                                     StrokeType pgtype,
 302                                     double strokewidth,
 303                                     StrokeLineCap pgcap,
 304                                     StrokeLineJoin pgjoin,
 305                                     float miterLimit,
 306                                     float[] dashArray,
 307                                     float dashOffset) {
 308         initStroke(pgtype, strokewidth, pgcap, pgjoin, miterLimit);
 309         return tmpStroke.createStrokedShape(shape);
 310     }
 311 
 312     public CursorSizeConverter getCursorSizeConverter() {
 313         return cursorSizeConverter;
 314     }
 315 
 316     public void setCursorSizeConverter(
 317             CursorSizeConverter cursorSizeConverter) {
 318         this.cursorSizeConverter = cursorSizeConverter;
 319     }
 320 
 321     @Override
 322     public Dimension2D getBestCursorSize(int preferredWidth, int preferredHeight) {
 323         return cursorSizeConverter.getBestCursorSize(preferredWidth,
 324                                                      preferredHeight);
 325     }
 326 
 327     @Override
 328     public int getMaximumCursorColors() {
 329         return maximumCursorColors;
 330     }
 331 
 332     public void setMaximumCursorColors(int maximumCursorColors) {
 333         this.maximumCursorColors = maximumCursorColors;
 334     }
 335 
 336     @Override
 337     public AbstractMasterTimer getMasterTimer() {
 338         return masterTimer;
 339     }
 340 
 341     @Override
 342     public FontLoader getFontLoader() {
 343         return new StubFontLoader();
 344     }
 345 
 346     @Override
 347     public TextLayoutFactory getTextLayoutFactory() {
 348         return new StubTextLayoutFactory();
 349     }
 350 
 351     @Override public boolean isSupported(ConditionalFeature feature) {
 352         if (feature == ConditionalFeature.SCENE3D) {
 353             return true;
 354         } else if (feature == ConditionalFeature.TRANSPARENT_WINDOW) {
 355             return true;
 356         }
 357         return false;
 358     }
 359     
 360     /*
 361      * additional testing functions
 362      */
 363     public void fireTestPulse() {
 364         firePulse();
 365     }
 366 
 367     public boolean isPulseRequested() {
 368         return pulseRequested;
 369     }
 370 
 371     public void clearPulseRequested() {
 372         pulseRequested = false;
 373     }
 374 
 375     // do nothing -- bringing in FrameJob and MasterTimer also bring in
 376     // Settings and crap which isn't setup for the testing stuff because
 377     // we don't run through a RuntimeProvider or do normal startup
 378     // public @Override public void triggerNextPulse():Void { }
 379     @Override public void requestNextPulse() {
 380         pulseRequested = true;
 381     }
 382 
 383     private TKClipboard clipboard = new TKClipboard() {
 384         private Map<DataFormat, Object> map = new HashMap<DataFormat, Object>();
 385         private Image image;
 386         private double offsetX;
 387         private double offsetY;
 388 
 389         @Override
 390         public void setSecurityContext(AccessControlContext ctx) {
 391         }
 392 
 393         @Override public Set<DataFormat> getContentTypes() {
 394             return map.keySet();
 395         }
 396 
 397         @Override public boolean putContent(Pair<DataFormat, Object>... content) {
 398             boolean good;
 399             for (Pair<DataFormat,Object> pair : content) {
 400                 good = map.put(pair.getKey(), pair.getValue()) == pair.getValue();
 401                 if (!good) return false;
 402             }
 403             return true;
 404         }
 405 
 406         @Override public Object getContent(DataFormat dataFormat) {
 407             return map.get(dataFormat);
 408         }
 409 
 410         @Override public boolean hasContent(DataFormat dataFormat) {
 411             return map.containsKey(dataFormat);
 412         }
 413 
 414         @Override public Set<TransferMode> getTransferModes() {
 415             Set<TransferMode> modes = new HashSet<TransferMode>();
 416             modes.add(TransferMode.COPY);
 417             return modes;
 418         }
 419 
 420         @Override
 421         public void setDragView(Image image) {
 422             this.image = image;
 423         }
 424 
 425         @Override
 426         public void setDragViewOffsetX(double offsetX) {
 427             this.offsetX = offsetX;
 428         }
 429 
 430         @Override
 431         public void setDragViewOffsetY(double offsetY) {
 432             this.offsetY = offsetY;
 433         }
 434 
 435         @Override
 436         public Image getDragView() {
 437             return image;
 438         }
 439 
 440         @Override
 441         public double getDragViewOffsetX() {
 442             return offsetX;
 443         }
 444 
 445         @Override
 446         public double getDragViewOffsetY() {
 447             return offsetY;
 448         }
 449     };
 450 
 451 
 452     @Override
 453     public TKClipboard getSystemClipboard() {
 454         return clipboard;
 455     }
 456 
 457     @Override public TKClipboard getNamedClipboard(String name) {
 458         return null;
 459     }
 460 
 461     public static TKClipboard createDragboard() {
 462         StubToolkit tk = (StubToolkit)Toolkit.getToolkit();
 463         if (tk.dndDelegate != null) {
 464             return tk.dndDelegate.createDragboard();
 465         }
 466         return null;
 467     }
 468 
 469     @Override
 470     public void enableDrop(TKScene s, TKDropTargetListener l) {
 471         if (dndDelegate != null) {
 472             dndDelegate.enableDrop(l);
 473         }
 474     }
 475 
 476     private ScreenConfigurationAccessor accessor = new ScreenConfigurationAccessor() {
 477         @Override
 478         public int getMinX(Object obj) {
 479             return ((ScreenConfiguration) obj).getMinX();
 480         }
 481 
 482         @Override
 483         public int getMinY(Object obj) {
 484             return ((ScreenConfiguration) obj).getMinY();
 485         }
 486 
 487         @Override
 488         public int getWidth(Object obj) {
 489             return ((ScreenConfiguration) obj).getWidth();
 490         }
 491 
 492         @Override
 493         public int getHeight(Object obj) {
 494             return ((ScreenConfiguration) obj).getHeight();
 495         }
 496 
 497         @Override
 498         public int getVisualMinX(Object obj) {
 499             return ((ScreenConfiguration) obj).getVisualMinX();
 500         }
 501 
 502         @Override
 503         public int getVisualMinY(Object obj) {
 504             return ((ScreenConfiguration) obj).getVisualMinY();
 505         }
 506 
 507         @Override
 508         public int getVisualWidth(Object obj) {
 509             return ((ScreenConfiguration) obj).getVisualWidth();
 510         }
 511 
 512         @Override
 513         public int getVisualHeight(Object obj) {
 514             return ((ScreenConfiguration) obj).getVisualHeight();
 515         }
 516 
 517         @Override
 518         public float getDPI(Object obj) {
 519             return ((ScreenConfiguration) obj).getDPI();
 520         }
 521 
 522         @Override
 523         public float getUIScale(Object obj) {
 524             return ((ScreenConfiguration) obj).getScale();
 525         }
 526 
 527         @Override
 528         public float getRenderScale(Object obj) {
 529             return ((ScreenConfiguration) obj).getScale();
 530         }
 531     };
 532 
 533     @Override
 534     public ScreenConfigurationAccessor setScreenConfigurationListener(
 535             TKScreenConfigurationListener listener) {
 536         screenConfigurationListener = listener;
 537         return accessor;
 538     }
 539 
 540     @Override
 541     public ScreenConfiguration getPrimaryScreen() {
 542         return screenConfigurations[0];
 543     }
 544 
 545     public void setScreens(ScreenConfiguration... screenConfigurations) {
 546         this.screenConfigurations = screenConfigurations.clone();
 547         if (screenConfigurationListener != null) {
 548             screenConfigurationListener.screenConfigurationChanged();
 549         }
 550     }
 551 
 552     public void resetScreens() {
 553         setScreens(DEFAULT_SCREEN_CONFIG);
 554     }
 555 
 556     @Override
 557     public List<ScreenConfiguration> getScreens() {
 558         return Arrays.asList(screenConfigurations);
 559     }
 560 
 561     @Override
 562     public ScreenConfigurationAccessor getScreenConfigurationAccessor() {
 563         return accessor;
 564     }
 565 
 566     @Override public void registerDragGestureListener(TKScene s, Set<TransferMode> tm, TKDragGestureListener l) {
 567         if (dndDelegate != null) {
 568             dndDelegate.registerListener(l);
 569         }
 570     }
 571 
 572     @Override public void startDrag(TKScene scene, Set<TransferMode> tm, TKDragSourceListener l, Dragboard dragboard) {
 573         if (dndDelegate != null) {
 574             dndDelegate.startDrag(scene, tm, l, dragboard);
 575         }
 576     }
 577 
 578     @Override
 579     public ImageLoader loadImage(String url, int width, int height,
 580             boolean preserveRatio, boolean smooth) {
 581         return imageLoaderFactory.createImageLoader(url, width, height,
 582                                                     preserveRatio, smooth);
 583     }
 584 
 585     @Override
 586     public ImageLoader loadImage(InputStream stream, int width, int height,
 587             boolean preserveRatio, boolean smooth) {
 588         return imageLoaderFactory.createImageLoader(stream, width, height,
 589                                                     preserveRatio, smooth);
 590     }
 591 
 592     @Override
 593     public AsyncOperation loadImageAsync(
 594             AsyncOperationListener listener, String url, int width, int height,
 595             boolean preserveRatio, boolean smooth) {
 596         return imageLoaderFactory.createAsyncImageLoader(
 597                 listener, url, width, height, preserveRatio, smooth);
 598     }
 599 
 600     @Override
 601     public ImageLoader loadPlatformImage(Object platformImage) {
 602         return imageLoaderFactory.createImageLoader(platformImage,
 603                                                     0, 0, false, false);
 604     }
 605 
 606     @Override
 607     public PlatformImage createPlatformImage(int w, int h) {
 608         PlatformImage image = new StubWritablePlatformImage(w, h);
 609         imageLoaderFactory.registerImage(image, new StubPlatformImageInfo(w, h));
 610         return image;
 611     }
 612 
 613     @Override
 614     public void waitFor(Task t) {
 615         throw new UnsupportedOperationException();
 616     }
 617 
 618     @Override
 619     public int getKeyCodeForChar(String character) {
 620         if (charToKeyCodeMap != null) {
 621             final KeyCode keyCode = charToKeyCodeMap.get(character);
 622             if (keyCode != null) {
 623                 return keyCode.impl_getCode();
 624             }
 625         }
 626 
 627         return 0;
 628     }
 629 
 630     @Override
 631     public PathElement[] convertShapeToFXPath(Object shape) {
 632         // Had to be mocked up for TextField tests (for the caret!)
 633         // Since the "shape" could be anything, I'm just returning
 634         // something here, doesn't matter what.
 635         return new PathElement[0];
 636     }
 637 
 638     @Override
 639     public HitInfo convertHitInfoToFX(Object hit) {
 640         return (HitInfo) hit;
 641     }
 642 
 643     @Override
 644     public Filterable toFilterable(Image img) {
 645         return StubFilterable.create((StubPlatformImage)img.impl_getPlatformImage());
 646     }
 647 
 648     @Override
 649     public FilterContext getFilterContext(Object config) {
 650         throw new UnsupportedOperationException();
 651     }
 652 
 653     @Override
 654     public boolean isForwardTraversalKey(KeyEvent e) {
 655         throw new UnsupportedOperationException();
 656     }
 657 
 658     @Override
 659     public boolean isBackwardTraversalKey(KeyEvent e) {
 660         throw new UnsupportedOperationException();
 661     }
 662 
 663     @Override
 664     public Object createSVGPathObject(SVGPath svgpath) {
 665         int windingRule = (svgpath.getFillRule() == FillRule.NON_ZERO) ?
 666                            Path2D.WIND_NON_ZERO : Path2D.WIND_EVEN_ODD;
 667 
 668         return new SVGPathImpl(svgpath.getContent(), windingRule);
 669     }
 670 
 671     @Override
 672     public Path2D createSVGPath2D(SVGPath svgpath) {
 673         int windingRule = (svgpath.getFillRule() == FillRule.NON_ZERO) ?
 674                            Path2D.WIND_NON_ZERO : Path2D.WIND_EVEN_ODD;
 675 
 676         return new SVGPathImpl(svgpath.getContent(), windingRule);
 677     }
 678 
 679     @Override
 680     public boolean imageContains(Object image, float x, float y) {
 681         return ((StubPlatformImage) image).getImageInfo()
 682                                           .contains((int) x, (int) y);
 683     }
 684 
 685     public void setCurrentTime(long millis) {
 686         masterTimer.setCurrentTime(millis);
 687     }
 688 
 689     public void handleAnimation() {
 690         if (animationRunnable != null) {
 691             try {
 692                 animationRunnable.run();
 693             } catch (Throwable t) {
 694                 Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), t);
 695             }
 696         }
 697     }
 698 
 699     public StubImageLoaderFactory getImageLoaderFactory() {
 700         return imageLoaderFactory;
 701     }
 702 
 703     public void setAnimationTime(final long millis) {
 704         setCurrentTime(millis);
 705         handleAnimation();
 706         fireTestPulse();
 707     }
 708 
 709     @Override
 710     public void installInputMethodRequests(TKScene scene, InputMethodRequests requests) {
 711         // just do nothing here.
 712     }
 713 
 714     private Map<String, KeyCode> charToKeyCodeMap;
 715 
 716     public void setCharToKeyCodeMap(Map<String, KeyCode> charToKeyCodeMap) {
 717         this.charToKeyCodeMap = charToKeyCodeMap;
 718     }
 719 
 720     @Override
 721     public Object renderToImage(ImageRenderingContext context) {
 722         throw new UnsupportedOperationException();
 723     }
 724 
 725     @Override public Object enterNestedEventLoop(Object key) {
 726         throw new UnsupportedOperationException("Not supported yet.");
 727     }
 728 
 729     @Override public void exitNestedEventLoop(Object key, Object rval) {
 730         throw new UnsupportedOperationException("Not supported yet.");
 731     }
 732 
 733     @Override
 734     public boolean isNestedLoopRunning() {
 735         return false;
 736     }
 737 
 738     private KeyCode platformShortcutKey = KeyCode.SHORTCUT;
 739 
 740     public void setPlatformShortcutKey(final KeyCode platformShortcutKey) {
 741         this.platformShortcutKey = platformShortcutKey;
 742     }
 743 
 744     public KeyCode getPlatformShortcutKey() {
 745         return platformShortcutKey;
 746     }
 747 
 748     private DndDelegate dndDelegate;
 749     public void setDndDelegate(DndDelegate dndDelegate) {
 750         this.dndDelegate = dndDelegate;
 751     }
 752 
 753 
 754     public interface DndDelegate {
 755         void startDrag(TKScene scene, Set<TransferMode> tm,
 756                 TKDragSourceListener l, Dragboard dragboard);
 757 
 758         TKClipboard createDragboard();
 759 
 760         DragEvent convertDragEventToFx(Object event, Dragboard dragboard);
 761 
 762         void registerListener(TKDragGestureListener l);
 763 
 764         void enableDrop(TKDropTargetListener l);
 765     }
 766 
 767     public interface CommonDialogsSupport {
 768         FileChooserResult showFileChooser(TKStage ownerWindow,
 769                                    String title,
 770                                    File initialDirectory,
 771                                    String initialFileName,
 772                                    FileChooserType fileChooserType,
 773                                    List<ExtensionFilter> extensionFilters,
 774                                    ExtensionFilter selectedFilter);
 775 
 776         File showDirectoryChooser(TKStage ownerWindow,
 777                                   String title,
 778                                   File initialDirectory);
 779     }
 780 
 781     private CommonDialogsSupport commonDialogsSupport;
 782     public void setCommonDialogsSupport(
 783             final CommonDialogsSupport commonDialogsSupport) {
 784         this.commonDialogsSupport = commonDialogsSupport;
 785     }
 786 
 787     @Override
 788     public FileChooserResult showFileChooser(TKStage ownerWindow,
 789                                       String title,
 790                                       File initialDirectory,
 791                                       String initialFileName,
 792                                       FileChooserType fileChooserType,
 793                                       List<ExtensionFilter> extensionFilters,
 794                                       ExtensionFilter selectedFilter) {
 795         return commonDialogsSupport.showFileChooser(
 796                                         ownerWindow,
 797                                         title,
 798                                         initialDirectory,
 799                                         initialFileName,
 800                                         fileChooserType,
 801                                         extensionFilters,
 802                                         selectedFilter);
 803     }
 804 
 805 
 806     @Override
 807     public File showDirectoryChooser(TKStage ownerWindow,
 808                                      String title,
 809                                      File initialDirectory) {
 810         return commonDialogsSupport.showDirectoryChooser(
 811                                         ownerWindow,
 812                                         title,
 813                                         initialDirectory);
 814     }
 815 
 816     @Override
 817     public long getMultiClickTime() {
 818         return 500L;
 819     }
 820 
 821     @Override
 822     public int getMultiClickMaxX() {
 823         return 5;
 824     }
 825 
 826     @Override
 827     public int getMultiClickMaxY() {
 828         return 5;
 829     }
 830 
 831     public static final class ScreenConfiguration {
 832         private final int minX;
 833         private final int minY;
 834         private final int width;
 835         private final int height;
 836         private final int visualMinX;
 837         private final int visualMinY;
 838         private final int visualWidth;
 839         private final int visualHeight;
 840         private final float dpi;
 841         private final float scale;
 842 
 843         public ScreenConfiguration(final int minX, final int minY,
 844                                    final int width, final int height,
 845                                    final int visualMinX,
 846                                    final int visualMinY,
 847                                    final int visualWidth,
 848                                    final int visualHeight,
 849                                    final float dpi) {
 850             this.minX = minX;
 851             this.minY = minY;
 852             this.width = width;
 853             this.height = height;
 854             this.visualMinX = visualMinX;
 855             this.visualMinY = visualMinY;
 856             this.visualWidth = visualWidth;
 857             this.visualHeight = visualHeight;
 858             this.dpi = dpi;
 859             this.scale = 1;  // TODO: add a constructor that takes scale
 860         }
 861 
 862         public int getMinX() {
 863             return minX;
 864         }
 865 
 866         public int getMinY() {
 867             return minY;
 868         }
 869 
 870         public int getWidth() {
 871             return width;
 872         }
 873 
 874         public int getHeight() {
 875             return height;
 876         }
 877 
 878         public int getVisualMinX() {
 879             return visualMinX;
 880         }
 881 
 882         public int getVisualMinY() {
 883             return visualMinY;
 884         }
 885 
 886         public int getVisualWidth() {
 887             return visualWidth;
 888         }
 889 
 890         public int getVisualHeight() {
 891             return visualHeight;
 892         }
 893 
 894         public float getDPI() {
 895             return dpi;
 896         }
 897 
 898         public float getScale() {
 899             return scale;
 900         }
 901     }
 902 
 903     public static class StubSystemMenu implements TKSystemMenu {
 904 
 905         private  List<MenuBase> menus = null;
 906 
 907         @Override
 908         public boolean isSupported() {
 909             // Although not all platforms have a system menu, the only real
 910             // interaction with the system menu is this TKSystemMenu instance
 911             // so we'll return true on all platforms.
 912             return true;
 913 //                    final String os = System.getProperty("os.name");
 914 //                    return (os != null && os.startsWith("Mac"));
 915         }
 916 
 917         @Override
 918         public void setMenus(List<MenuBase> menus) {
 919             this.menus = menus;
 920         }
 921 
 922         // make menus accessible to unit tests
 923         public List<MenuBase> getMenus() {
 924             return menus;
 925         }
 926 
 927     }
 928 }