1 /*
   2  * Copyright (c) 2008, 2016, 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     @Override public void close() {
 795         super.close();
 796         QuantumToolkit.runWithRenderLock(() -> {
 797             // prevents closing a closed platform window
 798             if (platformWindow != null) {
 799                 platformWindows.remove(platformWindow);
 800                 platformWindow.close();
 801                 platformWindow = null;
 802             }
 803             GlassScene oldScene = getViewScene();
 804             if (oldScene != null) {
 805                 oldScene.updateSceneState();
 806             }
 807             return null;
 808         });
 809     }
 810 
 811     // setPlatformWindowClosed is only set upon receiving platform window has
 812     // closed notification. This state is necessary to prevent the platform
 813     // window from being closed more than once.
 814     void setPlatformWindowClosed() {
 815         platformWindow = null;
 816     }
 817 
 818     static void addActiveWindow(WindowStage window) {
 819         activeWindows.remove(window);
 820         activeWindows.add(window);
 821     }
 822 
 823     static void removeActiveWindow(WindowStage window) {
 824         activeWindows.remove(window);
 825     }
 826 
 827     final void handleFocusDisabled() {
 828         if (activeWindows.isEmpty()) {
 829             return;
 830         }
 831         WindowStage window = activeWindows.get(activeWindows.size() - 1);
 832         window.setIconified(false);
 833         window.requestToFront();
 834         window.requestFocus();
 835     }
 836 
 837     @Override public boolean grabFocus() {
 838         return platformWindow.grabFocus();
 839     }
 840 
 841     @Override public void ungrabFocus() {
 842         platformWindow.ungrabFocus();
 843     }
 844 
 845     @Override public void requestFocus() {
 846         platformWindow.requestFocus();
 847     }
 848 
 849     @Override public void requestFocus(FocusCause cause) {
 850         switch (cause) {
 851             case TRAVERSED_FORWARD:
 852                 platformWindow.requestFocus(WindowEvent.FOCUS_GAINED_FORWARD);
 853                 break;
 854             case TRAVERSED_BACKWARD:
 855                 platformWindow.requestFocus(WindowEvent.FOCUS_GAINED_BACKWARD);
 856                 break;
 857             case ACTIVATED:
 858                 platformWindow.requestFocus(WindowEvent.FOCUS_GAINED);
 859                 break;
 860             case DEACTIVATED:
 861                 platformWindow.requestFocus(WindowEvent.FOCUS_LOST);
 862                 break;
 863         }
 864     }
 865 
 866     @Override
 867     protected void setPlatformEnabled(boolean enabled) {
 868         super.setPlatformEnabled(enabled);
 869         platformWindow.setEnabled(enabled);
 870         if (enabled) {
 871             // Check if window is really enabled - to handle nested case
 872             if (platformWindow.isEnabled()) {
 873                 requestToFront();
 874             }
 875         } else {
 876             removeActiveWindow(this);
 877         }
 878     }
 879 
 880     void setEnabled(boolean enabled) {
 881         if ((owner != null) && (owner instanceof WindowStage)) {
 882             ((WindowStage) owner).setEnabled(enabled);
 883         }
 884         /*
 885          * RT-17588 - exit if stage is closed from under us as
 886          *            any further access to the Glass layer
 887          *            will throw an exception
 888          */
 889         if (enabled && (platformWindow == null || platformWindow.isClosed())) {
 890             return;
 891         }
 892         setPlatformEnabled(enabled);
 893         if (enabled) {
 894             if (isAppletStage && null != appletWindow) {
 895                 appletWindow.assertStageOrder();
 896             }
 897         }
 898     }
 899 
 900     // Note: This method is required to workaround a glass issue mentioned in RT-12607
 901     protected void requestToFront() {
 902         if (platformWindow != null) {
 903             platformWindow.toFront();
 904             platformWindow.requestFocus();
 905         }
 906     }
 907 
 908     public void setInAllowedEventHandler(boolean inAllowedEventHandler) {
 909         this.inAllowedEventHandler = inAllowedEventHandler;
 910     }
 911 
 912     private boolean isInAllowedEventHandler() {
 913         return inAllowedEventHandler;
 914     }
 915 
 916     @Override
 917     public void requestInput(String text, int type, double width, double height,
 918                         double Mxx, double Mxy, double Mxz, double Mxt,
 919                         double Myx, double Myy, double Myz, double Myt,
 920                         double Mzx, double Mzy, double Mzz, double Mzt) {
 921         platformWindow.requestInput(text, type, width, height,
 922                                     Mxx, Mxy, Mxz, Mxt,
 923                                     Myx, Myy, Myz, Myt,
 924                                     Mzx, Mzy, Mzz, Mzt);
 925     }
 926 
 927     @Override
 928     public void releaseInput() {
 929         platformWindow.releaseInput();
 930     }
 931 
 932     @Override public void setRTL(boolean b) {
 933         rtl = b;
 934     }
 935 
 936 }