1 /*
   2  * Copyright (c) 2008, 2018, 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 java.nio.ByteBuffer;
  29 import java.security.AccessController;
  30 import java.security.Permission;
  31 import java.security.PrivilegedAction;
  32 import java.security.AccessControlContext;
  33 import java.util.HashMap;
  34 import java.util.LinkedList;
  35 import java.util.List;
  36 import java.util.Map;
  37 
  38 import javafx.scene.input.KeyCombination;
  39 import javafx.stage.Modality;
  40 import javafx.stage.Stage;
  41 import javafx.stage.StageStyle;
  42 
  43 import com.sun.glass.events.WindowEvent;
  44 import com.sun.glass.ui.*;
  45 import com.sun.glass.ui.Window.Level;
  46 import com.sun.javafx.PlatformUtil;
  47 import com.sun.javafx.iio.common.PushbroomScaler;
  48 import com.sun.javafx.iio.common.ScalerFactory;
  49 import com.sun.javafx.tk.FocusCause;
  50 import com.sun.javafx.tk.TKScene;
  51 import com.sun.javafx.tk.TKStage;
  52 import com.sun.prism.Image;
  53 import com.sun.prism.PixelFormat;
  54 import java.util.Locale;
  55 import java.util.ResourceBundle;
  56 import static com.sun.javafx.FXPermissions.*;
  57 
  58 class WindowStage extends GlassStage {
  59 
  60     protected Window platformWindow;
  61 
  62     protected javafx.stage.Stage fxStage;
  63 
  64     private StageStyle style;
  65     private GlassStage owner = null;
  66     private Modality modality = Modality.NONE;
  67     private final boolean securityDialog;
  68 
  69     private OverlayWarning warning = null;
  70     private boolean rtl = false;
  71     private boolean transparent = false;
  72     private boolean isPrimaryStage = false;
  73     private boolean isAppletStage = false; // true if this is an embedded applet window
  74     private boolean isPopupStage = false;
  75     private boolean isInFullScreen = false;
  76     private boolean isAlwaysOnTop = false;
  77 
  78     // A flag to indicate whether a call was generated from
  79     // an allowed input event handler.
  80     private boolean inAllowedEventHandler = false;
  81 
  82     // An active window is visible && enabled && focusable.
  83     // The list is maintained in the z-order, so that the last element
  84     // represents the topmost window (or more accurately, the last
  85     // focused window, which we assume is very close to the last topmost one).
  86     private static List<WindowStage> activeWindows = new LinkedList<>();
  87 
  88     private static Map<Window, WindowStage> platformWindows = new HashMap<>();
  89 
  90     private static GlassAppletWindow appletWindow = null;
  91     static void setAppletWindow(GlassAppletWindow aw) {
  92         appletWindow = aw;
  93     }
  94     static GlassAppletWindow getAppletWindow() {
  95         return appletWindow;
  96     }
  97 
  98     private static final Locale LOCALE = Locale.getDefault();
  99 
 100     private static final ResourceBundle RESOURCES =
 101         ResourceBundle.getBundle(WindowStage.class.getPackage().getName() +
 102                                  ".QuantumMessagesBundle", LOCALE);
 103 
 104 
 105     public WindowStage(javafx.stage.Window peerWindow, boolean securityDialog, final StageStyle stageStyle, Modality modality, TKStage owner) {
 106         this.style = stageStyle;
 107         this.owner = (GlassStage)owner;
 108         this.modality = modality;
 109         this.securityDialog = securityDialog;
 110 
 111         if (peerWindow instanceof javafx.stage.Stage) {
 112             fxStage = (Stage)peerWindow;
 113         } else {
 114             fxStage = null;
 115         }
 116 
 117         transparent = stageStyle == StageStyle.TRANSPARENT;
 118         if (owner == null) {
 119             if (this.modality == Modality.WINDOW_MODAL) {
 120                 this.modality = Modality.NONE;
 121             }
 122         }
 123     }
 124 
 125     final void setIsPrimary() {
 126         isPrimaryStage = true;
 127         if (appletWindow != null) {
 128             // this is an embedded applet stage
 129             isAppletStage = true;
 130         }
 131     }
 132 
 133     final void setIsPopup() {
 134         isPopupStage = true;
 135     }
 136 
 137     final boolean isSecurityDialog() {
 138         return securityDialog;
 139     }
 140 
 141     // Called by QuantumToolkit, so we can override initPlatformWindow in subclasses
 142     public final WindowStage init(GlassSystemMenu sysmenu) {
 143         initPlatformWindow();
 144         platformWindow.setEventHandler(new GlassWindowEventHandler(this));
 145         if (sysmenu.isSupported()) {
 146             sysmenu.createMenuBar();
 147             platformWindow.setMenuBar(sysmenu.getMenuBar());
 148         }
 149         return this;
 150     }
 151 
 152     private void initPlatformWindow() {
 153         if (platformWindow == null) {
 154             Application app = Application.GetApplication();
 155             if (isPrimaryStage && (null != appletWindow)) {
 156                 platformWindow = app.createWindow(appletWindow.getGlassWindow().getNativeWindow());
 157             } else {
 158                 Window ownerWindow = null;
 159                 if (owner instanceof WindowStage) {
 160                     ownerWindow = ((WindowStage)owner).platformWindow;
 161                 }
 162                 boolean resizable = false;
 163                 boolean focusable = true;
 164                 int windowMask = rtl ? Window.RIGHT_TO_LEFT : 0;
 165                 if (isPopupStage) { // TODO: make it a stage style?
 166                     windowMask |= Window.POPUP;
 167                     if (style == StageStyle.TRANSPARENT) {
 168                         windowMask |= Window.TRANSPARENT;
 169                     }
 170                     focusable = false;
 171                 } else {
 172                     switch (style) {
 173                         case UNIFIED:
 174                             if (app.supportsUnifiedWindows()) {
 175                                 windowMask |= Window.UNIFIED;
 176                             }
 177                             // fall through
 178                         case DECORATED:
 179                             windowMask |=
 180                                 Window.TITLED | Window.CLOSABLE |
 181                                 Window.MINIMIZABLE | Window.MAXIMIZABLE;
 182                             if (ownerWindow != null || modality != Modality.NONE) {
 183                                 windowMask &=
 184                                     ~(Window.MINIMIZABLE | Window.MAXIMIZABLE);
 185                             }
 186                             resizable = true;
 187                             break;
 188                         case UTILITY:
 189                             windowMask |=  Window.TITLED | Window.UTILITY | Window.CLOSABLE;
 190                             break;
 191                         default:
 192                             windowMask |=
 193                                     (transparent ? Window.TRANSPARENT : Window.UNTITLED) | Window.CLOSABLE;
 194                             break;
 195                     }
 196                 }
 197                 platformWindow =
 198                         app.createWindow(ownerWindow, Screen.getMainScreen(), windowMask);
 199                 platformWindow.setResizable(resizable);
 200                 platformWindow.setFocusable(focusable);
 201                 if (securityDialog) {
 202                     platformWindow.setLevel(Window.Level.FLOATING);
 203                 }
 204                 if (fxStage != null && fxStage.getScene() != null) {
 205                     javafx.scene.paint.Paint paint = fxStage.getScene().getFill();
 206                     if (paint instanceof javafx.scene.paint.Color) {
 207                         javafx.scene.paint.Color color = (javafx.scene.paint.Color) paint;
 208                         platformWindow.setBackground((float) color.getRed(), (float) color.getGreen(), (float) color.getBlue());
 209                     } else if (paint instanceof javafx.scene.paint.LinearGradient) {
 210                         javafx.scene.paint.LinearGradient lgradient = (javafx.scene.paint.LinearGradient) paint;
 211                         computeAndSetBackground(lgradient.getStops());
 212                     } else if (paint instanceof javafx.scene.paint.RadialGradient) {
 213                         javafx.scene.paint.RadialGradient rgradient = (javafx.scene.paint.RadialGradient) paint;
 214                         computeAndSetBackground(rgradient.getStops());
 215                     }
 216                 }
 217 
 218             }
 219         }
 220         platformWindows.put(platformWindow, this);
 221     }
 222 
 223     private void computeAndSetBackground(List<javafx.scene.paint.Stop> stops) {
 224         if (stops.size() == 1) {
 225             javafx.scene.paint.Color color = stops.get(0).getColor();
 226             platformWindow.setBackground((float) color.getRed(),
 227                     (float) color.getGreen(), (float) color.getBlue());
 228         } else if (stops.size() > 1) {
 229             // A simple attempt to find a reasonable average color that is
 230             // within the stops arrange.
 231             javafx.scene.paint.Color color = stops.get(0).getColor();
 232             javafx.scene.paint.Color color2 = stops.get(stops.size() - 1).getColor();
 233             platformWindow.setBackground((float) ((color.getRed() + color2.getRed()) / 2.0),
 234                     (float) ((color.getGreen() + color2.getGreen()) / 2.0),
 235                     (float) ((color.getBlue() + color2.getBlue()) / 2.0));
 236         }
 237     }
 238 
 239     final Window getPlatformWindow() {
 240         return platformWindow;
 241     }
 242 
 243     static WindowStage findWindowStage(Window platformWindow) {
 244         return platformWindows.get(platformWindow);
 245     }
 246 
 247     protected GlassStage getOwner() {
 248         return owner;
 249     }
 250 
 251     protected ViewScene getViewScene() {
 252         return (ViewScene)getScene();
 253     }
 254 
 255     StageStyle getStyle() {
 256         return style;
 257     }
 258 
 259     @Override public TKScene createTKScene(boolean depthBuffer, boolean msaa, AccessControlContext acc) {
 260         ViewScene scene = new ViewScene(depthBuffer, msaa);
 261         scene.setSecurityContext(acc);
 262         return scene;
 263     }
 264 
 265     /**
 266      * Set the scene to be displayed in this stage
 267      *
 268      * @param scene The peer of the scene to be displayed
 269      */
 270     @Override public void setScene(TKScene scene) {
 271         GlassScene oldScene = getScene();
 272         if (oldScene == scene) {
 273             // Nothing to do
 274             return;
 275         }
 276         // RT-21465, RT-28490
 277         // We don't support scene changes in full-screen mode.
 278         exitFullScreen();
 279         super.setScene(scene);
 280         if (scene != null) {
 281             GlassScene newScene = getViewScene();
 282             View view = newScene.getPlatformView();
 283             QuantumToolkit.runWithRenderLock(() -> {
 284                 platformWindow.setView(view);
 285                 if (oldScene != null) oldScene.updateSceneState();
 286                 newScene.updateSceneState();
 287                 return null;
 288             });
 289             requestFocus();
 290         } else {
 291             QuantumToolkit.runWithRenderLock(() -> {
 292                 // platformWindow can be null here, if this window is owned,
 293                 // and its owner is being closed.
 294                 if (platformWindow != null) {
 295                     platformWindow.setView(null);
 296                 }
 297                 if (oldScene != null) {
 298                     oldScene.updateSceneState();
 299                 }
 300                 return null;
 301             });
 302         }
 303         if (oldScene != null) {
 304             ViewPainter painter = ((ViewScene)oldScene).getPainter();
 305             QuantumRenderer.getInstance().disposePresentable(painter.presentable);   // latched on RT
 306         }
 307     }
 308 
 309     @Override public void setBounds(float x, float y, boolean xSet, boolean ySet,
 310                                     float w, float h, float cw, float ch,
 311                                     float xGravity, float yGravity,
 312                                     float renderScaleX, float renderScaleY)
 313     {
 314         if (renderScaleX > 0.0 || renderScaleY > 0.0) {
 315             // We set the render scale first since the call to setBounds()
 316             // below can induce a recursive update on the scales if it moves
 317             // the window to a new screen and we will then end up being called
 318             // back with a new scale.  We do not want to set these old scale
 319             // values after that recursion happens.
 320             if (renderScaleX > 0.0) {
 321                 platformWindow.setRenderScaleX(renderScaleX);
 322             }
 323             if (renderScaleY > 0.0) {
 324                 platformWindow.setRenderScaleY(renderScaleY);
 325             }
 326             ViewScene vscene = getViewScene();
 327             if (vscene != null) {
 328                 vscene.updateSceneState();
 329                 vscene.entireSceneNeedsRepaint();
 330             }
 331         }
 332         if (isAppletStage) {
 333             xSet = ySet = false;
 334         }
 335         if (xSet || ySet || w > 0 || h > 0 || cw > 0 || ch > 0) {
 336             platformWindow.setBounds(x, y, xSet, ySet, w, h, cw, ch, xGravity, yGravity);
 337         }
 338     }
 339 
 340     @Override
 341     public float getPlatformScaleX() {
 342         return platformWindow.getPlatformScaleX();
 343     }
 344 
 345     @Override
 346     public float getPlatformScaleY() {
 347         return platformWindow.getPlatformScaleY();
 348     }
 349 
 350     @Override
 351     public float getOutputScaleX() {
 352         return platformWindow.getOutputScaleX();
 353     }
 354 
 355     @Override
 356     public float getOutputScaleY() {
 357         return platformWindow.getOutputScaleY();
 358     }
 359 
 360     @Override public void setMinimumSize(int minWidth, int minHeight) {
 361         minWidth  = (int) Math.ceil(minWidth  * getPlatformScaleX());
 362         minHeight = (int) Math.ceil(minHeight * getPlatformScaleY());
 363         platformWindow.setMinimumSize(minWidth, minHeight);
 364     }
 365 
 366     @Override public void setMaximumSize(int maxWidth, int maxHeight) {
 367         maxWidth  = (int) Math.ceil(maxWidth  * getPlatformScaleX());
 368         maxHeight = (int) Math.ceil(maxHeight * getPlatformScaleY());
 369         platformWindow.setMaximumSize(maxWidth, maxHeight);
 370     }
 371 
 372     static Image findBestImage(java.util.List icons, int width, int height) {
 373         Image image = null;
 374         double bestSimilarity = 3; //Impossibly high value
 375         for (Object icon : icons) {
 376             //Iterate imageList looking for best matching image.
 377             //'Similarity' measure is defined as good scale factor and small insets.
 378             //best possible similarity is 0 (no scale, no insets).
 379             //It's found by experimentation that good-looking results are achieved
 380             //with scale factors x1, x3/4, x2/3, xN, x1/N.
 381             //Check to make sure the image/image format is correct.
 382             Image im = (Image)icon;
 383             if (im == null || !(im.getPixelFormat() == PixelFormat.BYTE_RGB ||
 384                 im.getPixelFormat() == PixelFormat.BYTE_BGRA_PRE ||
 385                 im.getPixelFormat() == PixelFormat.BYTE_GRAY))
 386             {
 387                 continue;
 388             }
 389 
 390             int iw = im.getWidth();
 391             int ih = im.getHeight();
 392 
 393             if (iw > 0 && ih > 0) {
 394                 //Calc scale factor
 395                 double scaleFactor = Math.min((double)width / (double)iw,
 396                                               (double)height / (double)ih);
 397                 //Calculate scaled image dimensions
 398                 //adjusting scale factor to nearest "good" value
 399                 int adjw;
 400                 int adjh;
 401                 double scaleMeasure = 1; //0 - best (no) scale, 1 - impossibly bad
 402                 if (scaleFactor >= 2) {
 403                     //Need to enlarge image more than twice
 404                     //Round down scale factor to multiply by integer value
 405                     scaleFactor = Math.floor(scaleFactor);
 406                     adjw = iw * (int)scaleFactor;
 407                     adjh = ih * (int)scaleFactor;
 408                     scaleMeasure = 1.0 - 0.5 / scaleFactor;
 409                 } else if (scaleFactor >= 1) {
 410                     //Don't scale
 411                     scaleFactor = 1.0;
 412                     adjw = iw;
 413                     adjh = ih;
 414                     scaleMeasure = 0;
 415                 } else if (scaleFactor >= 0.75) {
 416                     //Multiply by 3/4
 417                     scaleFactor = 0.75;
 418                     adjw = iw * 3 / 4;
 419                     adjh = ih * 3 / 4;
 420                     scaleMeasure = 0.3;
 421                 } else if (scaleFactor >= 0.6666) {
 422                     //Multiply by 2/3
 423                     scaleFactor = 0.6666;
 424                     adjw = iw * 2 / 3;
 425                     adjh = ih * 2 / 3;
 426                     scaleMeasure = 0.33;
 427                 } else {
 428                     //Multiply size by 1/scaleDivider
 429                     //where scaleDivider is minimum possible integer
 430                     //larger than 1/scaleFactor
 431                     double scaleDivider = Math.ceil(1.0 / scaleFactor);
 432                     scaleFactor = 1.0 / scaleDivider;
 433                     adjw = (int)Math.round((double)iw / scaleDivider);
 434                     adjh = (int)Math.round((double)ih / scaleDivider);
 435                     scaleMeasure = 1.0 - 1.0 / scaleDivider;
 436                 }
 437                 double similarity = ((double)width - (double)adjw) / (double)width +
 438                     ((double)height - (double)adjh) / (double)height + //Large padding is bad
 439                     scaleMeasure; //Large rescale is bad
 440                 if (similarity < bestSimilarity) {
 441                     bestSimilarity = similarity;
 442                     image = im;
 443                 }
 444                 if (similarity == 0) break;
 445             }
 446         }
 447         return image;
 448     }
 449 
 450     @Override public void setIcons(java.util.List icons) {
 451 
 452         int SMALL_ICON_HEIGHT = 32;
 453         int SMALL_ICON_WIDTH = 32;
 454         if (PlatformUtil.isMac()) { //Mac Sized Icons
 455             SMALL_ICON_HEIGHT = 128;
 456             SMALL_ICON_WIDTH = 128;
 457         } else if (PlatformUtil.isWindows()) { //Windows Sized Icons
 458             SMALL_ICON_HEIGHT = 32;
 459             SMALL_ICON_WIDTH = 32;
 460         } else if (PlatformUtil.isLinux()) { //Linux icons
 461             SMALL_ICON_HEIGHT = 128;
 462             SMALL_ICON_WIDTH = 128;
 463         }
 464 
 465         if (icons == null || icons.size() < 1) { //no icons passed in
 466             platformWindow.setIcon(null);
 467             return;
 468         }
 469 
 470         Image image = findBestImage(icons, SMALL_ICON_WIDTH, SMALL_ICON_HEIGHT);
 471         if (image == null) {
 472             //No images were found, possibly all are broken
 473             return;
 474         }
 475 
 476         PushbroomScaler scaler = ScalerFactory.createScaler(image.getWidth(), image.getHeight(),
 477                                                             image.getBytesPerPixelUnit(),
 478                                                             SMALL_ICON_WIDTH, SMALL_ICON_HEIGHT, true);
 479 
 480         //shrink the image and convert the format to INT_ARGB_PRE
 481         ByteBuffer buf = (ByteBuffer) image.getPixelBuffer();
 482         byte bytes[] = new byte[buf.limit()];
 483 
 484         int iheight = image.getHeight();
 485 
 486         //Iterate through each scanline of the image
 487         //and pass it one at a time to the scaling object
 488         for (int z = 0; z < iheight; z++) {
 489             buf.position(z*image.getScanlineStride());
 490             buf.get(bytes, 0, image.getScanlineStride());
 491             scaler.putSourceScanline(bytes, 0);
 492         }
 493 
 494         buf.rewind();
 495 
 496         final Image img = image.iconify(scaler.getDestination(), SMALL_ICON_WIDTH, SMALL_ICON_HEIGHT);
 497         platformWindow.setIcon(PixelUtils.imageToPixels(img));
 498     }
 499 
 500     @Override public void setTitle(String title) {
 501         platformWindow.setTitle(title);
 502     }
 503 
 504     @Override public void setVisible(final boolean visible) {
 505         // Before setting visible to false on the native window, we unblock
 506         // other windows.
 507         if (!visible) {
 508             removeActiveWindow(this);
 509             if (modality == Modality.WINDOW_MODAL) {
 510                 if (owner != null && owner instanceof WindowStage) {
 511                     ((WindowStage) owner).setEnabled(true);
 512                 }
 513             } else if (modality == Modality.APPLICATION_MODAL) {
 514                 windowsSetEnabled(true);
 515             } else {
 516                 // Note: This method is required to workaround a glass issue
 517                 // mentioned in RT-12607
 518                 // If the hiding stage is unfocusable (i.e. it's a PopupStage),
 519                 // then we don't do this to avoid stealing the focus.
 520                 if (!isPopupStage && owner != null && owner instanceof WindowStage) {
 521                     WindowStage ownerStage = (WindowStage)owner;
 522                     ownerStage.requestToFront();
 523                 }
 524             }
 525         }
 526         QuantumToolkit.runWithRenderLock(() -> {
 527             // platformWindow can be null here, if this window is owned,
 528             // and its owner is being closed.
 529             if (platformWindow != null) {
 530                 platformWindow.setVisible(visible);
 531             }
 532             super.setVisible(visible);
 533             return null;
 534         });
 535         // After setting visible to true on the native window, we block
 536         // other windows.
 537         if (visible) {
 538             if (modality == Modality.WINDOW_MODAL) {
 539                 if (owner != null && owner instanceof WindowStage) {
 540                     ((WindowStage) owner).setEnabled(false);
 541                 }
 542             } else if (modality == Modality.APPLICATION_MODAL) {
 543                 windowsSetEnabled(false);
 544             }
 545             if (isAppletStage && null != appletWindow) {
 546                 appletWindow.assertStageOrder();
 547             }
 548         }
 549 
 550         applyFullScreen();
 551     }
 552 
 553     @Override boolean isVisible() {
 554         return platformWindow.isVisible();
 555     }
 556 
 557     @Override public void setOpacity(float opacity) {
 558         platformWindow.setAlpha(opacity);
 559         GlassScene gs = getScene();
 560         if (gs != null) {
 561             gs.entireSceneNeedsRepaint();
 562         }
 563     }
 564 
 565     public boolean needsUpdateWindow() {
 566         return transparent && (Application.GetApplication().shouldUpdateWindow());
 567     }
 568 
 569     @Override public void setIconified(boolean iconified) {
 570         if (platformWindow.isMinimized() == iconified) {
 571             return;
 572         }
 573         platformWindow.minimize(iconified);
 574     }
 575 
 576     @Override public void setMaximized(boolean maximized) {
 577         if (platformWindow.isMaximized() == maximized) {
 578             return;
 579         }
 580         platformWindow.maximize(maximized);
 581     }
 582 
 583     @Override
 584     public void setAlwaysOnTop(boolean alwaysOnTop) {
 585         // The securityDialog flag takes precedence over alwaysOnTop
 586         if (securityDialog) return;
 587 
 588         if (isAlwaysOnTop == alwaysOnTop) {
 589             return;
 590         }
 591 
 592         if (alwaysOnTop) {
 593             if (hasPermission(SET_WINDOW_ALWAYS_ON_TOP_PERMISSION)) {
 594                 platformWindow.setLevel(Level.FLOATING);
 595             } else {
 596                 alwaysOnTop = false;
 597                 if (stageListener != null) {
 598                     stageListener.changedAlwaysOnTop(alwaysOnTop);
 599                 }
 600             }
 601         } else {
 602             platformWindow.setLevel(Level.NORMAL);
 603         }
 604         isAlwaysOnTop = alwaysOnTop;
 605     }
 606 
 607     @Override public void setResizable(boolean resizable) {
 608         platformWindow.setResizable(resizable);
 609         // note: for child windows this is ignored and we fail silently
 610     }
 611 
 612     // Return true if this stage is trusted for full screen - doesn't have a
 613     // security manager, or a permission check doesn't result in a security
 614     // exeception.
 615     boolean isTrustedFullScreen() {
 616         return hasPermission(UNRESTRICTED_FULL_SCREEN_PERMISSION);
 617     }
 618 
 619     // Safely exit full screen
 620     void exitFullScreen() {
 621         setFullScreen(false);
 622     }
 623 
 624     boolean isApplet() {
 625         return isPrimaryStage && null != appletWindow;
 626     }
 627 
 628     private boolean hasPermission(Permission perm) {
 629         try {
 630             final SecurityManager sm = System.getSecurityManager();
 631             if (sm != null) {
 632                 sm.checkPermission(perm, getAccessControlContext());
 633             }
 634             return true;
 635         } catch (SecurityException se) {
 636             return false;
 637         }
 638     }
 639 
 640     private boolean fullScreenFromUserEvent = false;
 641 
 642     private KeyCombination savedFullScreenExitKey = null;
 643 
 644     public final KeyCombination getSavedFullScreenExitKey() {
 645         return savedFullScreenExitKey;
 646     }
 647 
 648     private void applyFullScreen() {
 649         if (platformWindow == null) {
 650             // applyFullScreen() can be called from setVisible(false), while the
 651             // platformWindow has already been destroyed.
 652             return;
 653         }
 654         View v = platformWindow.getView();
 655         if (isVisible() && v != null && v.isInFullscreen() != isInFullScreen) {
 656             if (isInFullScreen) {
 657                 // Check whether app is full screen trusted or flag is set
 658                 // indicating that the fullscreen request came from an input
 659                 // event handler.
 660                 // If not notify the stageListener to reset fullscreen to false.
 661                 final boolean isTrusted = isTrustedFullScreen();
 662                 if (!isTrusted && !fullScreenFromUserEvent) {
 663                     exitFullScreen();
 664                     fullscreenChanged(false);
 665                 } else {
 666                     v.enterFullscreen(false, false, false);
 667                     if (warning != null && warning.inWarningTransition()) {
 668                         warning.setView(getViewScene());
 669                     } else {
 670                         boolean showWarning = true;
 671 
 672                         KeyCombination key = null;
 673                         String exitMessage = null;
 674 
 675                         if (isTrusted && (fxStage != null)) {
 676                             // copy the user set definitions for later use.
 677                             key = fxStage.getFullScreenExitKeyCombination();
 678 
 679                             exitMessage = fxStage.getFullScreenExitHint();
 680                         }
 681 
 682                         savedFullScreenExitKey =
 683                                 key == null
 684                                 ? defaultFullScreenExitKeycombo
 685                                 : key;
 686 
 687                         if (
 688                             // the hint is ""
 689                             "".equals(exitMessage) ||
 690                             // if the key is NO_MATCH
 691                             (savedFullScreenExitKey.equals(KeyCombination.NO_MATCH))
 692                                 ) {
 693                             showWarning = false;
 694                         }
 695 
 696                         // the hint is not set, use the key for the message
 697                         if (showWarning && exitMessage == null) {
 698                             if (key == null) {
 699                                 exitMessage = RESOURCES.getString("OverlayWarningESC");
 700                             } else {
 701                                 String f = RESOURCES.getString("OverlayWarningKey");
 702                                 exitMessage = f.format(f, savedFullScreenExitKey.toString());
 703                             }
 704                         }
 705 
 706                         if (showWarning && warning == null) {
 707                             setWarning(new OverlayWarning(getViewScene()));
 708                         }
 709 
 710                         if (showWarning && warning != null) {
 711                             warning.warn(exitMessage);
 712                         }
 713                     }
 714                 }
 715             } else {
 716                 if (warning != null) {
 717                     warning.cancel();
 718                     setWarning(null);
 719                 }
 720                 v.exitFullscreen(false);
 721             }
 722             // Reset flag once we are done process fullscreen
 723             fullScreenFromUserEvent = false;
 724         } else if (!isVisible() && warning != null) {
 725             // if the window is closed - re-open with fresh warning
 726             warning.cancel();
 727             setWarning(null);
 728         }
 729     }
 730 
 731     void setWarning(OverlayWarning newWarning) {
 732         this.warning = newWarning;
 733         getViewScene().synchroniseOverlayWarning();
 734     }
 735 
 736     OverlayWarning getWarning() {
 737         return warning;
 738     }
 739 
 740     @Override public void setFullScreen(boolean fullScreen) {
 741         if (isInFullScreen == fullScreen) {
 742             return;
 743         }
 744 
 745        // Set a flag indicating whether this method was called from
 746         // an allowed input event handler.
 747         if (isInAllowedEventHandler()) {
 748             fullScreenFromUserEvent = true;
 749         }
 750 
 751         GlassStage fsWindow = activeFSWindow.get();
 752         if (fullScreen && (fsWindow != null)) {
 753             fsWindow.setFullScreen(false);
 754         }
 755         isInFullScreen = fullScreen;
 756         applyFullScreen();
 757         if (fullScreen) {
 758             activeFSWindow.set(this);
 759         }
 760     }
 761 
 762     void fullscreenChanged(final boolean fs) {
 763         if (!fs) {
 764             if (activeFSWindow.compareAndSet(this, null)) {
 765                 isInFullScreen = false;
 766             }
 767         } else {
 768             isInFullScreen = true;
 769             activeFSWindow.set(this);
 770         }
 771         AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 772             if (stageListener != null) {
 773                 stageListener.changedFullscreen(fs);
 774             }
 775             return null;
 776         }, getAccessControlContext());
 777     }
 778 
 779     @Override public void toBack() {
 780         platformWindow.toBack();
 781         if (isAppletStage && null != appletWindow) {
 782             appletWindow.assertStageOrder();
 783         }
 784     }
 785 
 786     @Override public void toFront() {
 787         platformWindow.requestFocus(); // RT-17836
 788         platformWindow.toFront();
 789         if (isAppletStage && null != appletWindow) {
 790             appletWindow.assertStageOrder();
 791         }
 792     }
 793 
 794     private boolean isClosePostponed = false;
 795     private Window deadWindow = null;
 796 
 797     @Override
 798     public void postponeClose() {
 799         isClosePostponed = true;
 800     }
 801 
 802     @Override
 803     public void closePostponed() {
 804         if (deadWindow != null) {
 805             deadWindow.close();
 806             deadWindow = null;
 807         }
 808     }
 809 
 810     @Override public void close() {
 811         super.close();
 812         QuantumToolkit.runWithRenderLock(() -> {
 813             // prevents closing a closed platform window
 814             if (platformWindow != null) {
 815                 platformWindows.remove(platformWindow);
 816                 if (isClosePostponed) {
 817                     deadWindow = platformWindow;
 818                 } else {
 819                     platformWindow.close();
 820                 }
 821                 platformWindow = null;
 822             }
 823             GlassScene oldScene = getViewScene();
 824             if (oldScene != null) {
 825                 oldScene.updateSceneState();
 826             }
 827             return null;
 828         });
 829     }
 830 
 831     // setPlatformWindowClosed is only set upon receiving platform window has
 832     // closed notification. This state is necessary to prevent the platform
 833     // window from being closed more than once.
 834     void setPlatformWindowClosed() {
 835         platformWindow = null;
 836     }
 837 
 838     static void addActiveWindow(WindowStage window) {
 839         activeWindows.remove(window);
 840         activeWindows.add(window);
 841     }
 842 
 843     static void removeActiveWindow(WindowStage window) {
 844         activeWindows.remove(window);
 845     }
 846 
 847     final void handleFocusDisabled() {
 848         if (activeWindows.isEmpty()) {
 849             return;
 850         }
 851         WindowStage window = activeWindows.get(activeWindows.size() - 1);
 852         window.setIconified(false);
 853         window.requestToFront();
 854         window.requestFocus();
 855     }
 856 
 857     @Override public boolean grabFocus() {
 858         return platformWindow.grabFocus();
 859     }
 860 
 861     @Override public void ungrabFocus() {
 862         platformWindow.ungrabFocus();
 863     }
 864 
 865     @Override public void requestFocus() {
 866         platformWindow.requestFocus();
 867     }
 868 
 869     @Override public void requestFocus(FocusCause cause) {
 870         switch (cause) {
 871             case TRAVERSED_FORWARD:
 872                 platformWindow.requestFocus(WindowEvent.FOCUS_GAINED_FORWARD);
 873                 break;
 874             case TRAVERSED_BACKWARD:
 875                 platformWindow.requestFocus(WindowEvent.FOCUS_GAINED_BACKWARD);
 876                 break;
 877             case ACTIVATED:
 878                 platformWindow.requestFocus(WindowEvent.FOCUS_GAINED);
 879                 break;
 880             case DEACTIVATED:
 881                 platformWindow.requestFocus(WindowEvent.FOCUS_LOST);
 882                 break;
 883         }
 884     }
 885 
 886     @Override
 887     protected void setPlatformEnabled(boolean enabled) {
 888         super.setPlatformEnabled(enabled);
 889         if (platformWindow != null) {
 890             platformWindow.setEnabled(enabled);
 891         }
 892         if (enabled) {
 893             // Check if window is really enabled - to handle nested case
 894             if (platformWindow != null && platformWindow.isEnabled()) {
 895                 requestToFront();
 896             }
 897         } else {
 898             removeActiveWindow(this);
 899         }
 900     }
 901 
 902     @Override
 903     public void setEnabled(boolean enabled) {
 904         if ((owner != null) && (owner instanceof WindowStage)) {
 905             ((WindowStage) owner).setEnabled(enabled);
 906         }
 907         /*
 908          * RT-17588 - exit if stage is closed from under us as
 909          *            any further access to the Glass layer
 910          *            will throw an exception
 911          */
 912         if (enabled && (platformWindow == null || platformWindow.isClosed())) {
 913             return;
 914         }
 915         setPlatformEnabled(enabled);
 916         if (enabled) {
 917             if (isAppletStage && null != appletWindow) {
 918                 appletWindow.assertStageOrder();
 919             }
 920         }
 921     }
 922 
 923     @Override
 924     public long getRawHandle() {
 925        return platformWindow.getRawHandle();
 926     }
 927 
 928     // Note: This method is required to workaround a glass issue mentioned in RT-12607
 929     protected void requestToFront() {
 930         if (platformWindow != null) {
 931             platformWindow.toFront();
 932             platformWindow.requestFocus();
 933         }
 934     }
 935 
 936     public void setInAllowedEventHandler(boolean inAllowedEventHandler) {
 937         this.inAllowedEventHandler = inAllowedEventHandler;
 938     }
 939 
 940     private boolean isInAllowedEventHandler() {
 941         return inAllowedEventHandler;
 942     }
 943 
 944     @Override
 945     public void requestInput(String text, int type, double width, double height,
 946                         double Mxx, double Mxy, double Mxz, double Mxt,
 947                         double Myx, double Myy, double Myz, double Myt,
 948                         double Mzx, double Mzy, double Mzz, double Mzt) {
 949         platformWindow.requestInput(text, type, width, height,
 950                                     Mxx, Mxy, Mxz, Mxt,
 951                                     Myx, Myy, Myz, Myt,
 952                                     Mzx, Mzy, Mzz, Mzt);
 953     }
 954 
 955     @Override
 956     public void releaseInput() {
 957         platformWindow.releaseInput();
 958     }
 959 
 960     @Override public void setRTL(boolean b) {
 961         rtl = b;
 962     }
 963 
 964 }