1 /*
   2  * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.javafx.tk.quantum;
  27 
  28 import java.nio.ByteBuffer;
  29 import java.security.AccessControlException;
  30 import java.security.AccessController;
  31 import java.security.AllPermission;
  32 import java.security.Permission;
  33 import java.security.PrivilegedAction;
  34 import java.security.AccessControlContext;
  35 import java.util.HashMap;
  36 import java.util.LinkedList;
  37 import java.util.List;
  38 import java.util.Map;
  39 
  40 import javafx.scene.input.KeyCombination;
  41 import javafx.stage.Modality;
  42 import javafx.stage.Stage;
  43 import javafx.stage.StageStyle;
  44 
  45 import com.sun.glass.events.WindowEvent;
  46 import com.sun.glass.ui.*;
  47 import com.sun.glass.ui.Window.Level;
  48 import com.sun.javafx.PlatformUtil;
  49 import com.sun.javafx.iio.common.PushbroomScaler;
  50 import com.sun.javafx.iio.common.ScalerFactory;
  51 import com.sun.javafx.tk.FocusCause;
  52 import com.sun.javafx.tk.TKScene;
  53 import com.sun.javafx.tk.TKStage;
  54 import com.sun.prism.Image;
  55 import com.sun.prism.PixelFormat;
  56 import java.util.Locale;
  57 import java.util.ResourceBundle;
  58 
  59 class WindowStage extends GlassStage {
  60 
  61     protected Window platformWindow;
  62 
  63     protected javafx.stage.Stage fxStage;
  64 
  65     private StageStyle style;
  66     private GlassStage owner = null;
  67     private Modality modality = Modality.NONE;
  68     private final boolean securityDialog;
  69 
  70     private OverlayWarning warning = null;
  71     private boolean rtl = false;
  72     private boolean transparent = false;
  73     private boolean isPrimaryStage = false;
  74     private boolean isAppletStage = false; // true if this is an embedded applet window
  75     private boolean isPopupStage = false;
  76     private boolean isInFullScreen = false;
  77 
  78     // A flag to indicate whether a call was generated from
  79     // an input event handler.
  80     private boolean inEventHandler = 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             }
 205         }
 206         platformWindows.put(platformWindow, this);
 207     }
 208 
 209     final Window getPlatformWindow() {
 210         return platformWindow;
 211     }
 212 
 213     static WindowStage findWindowStage(Window platformWindow) {
 214         return platformWindows.get(platformWindow);
 215     }
 216 
 217     protected GlassStage getOwner() {
 218         return owner;
 219     }
 220     
 221     protected ViewScene getViewScene() {
 222         return (ViewScene)getScene();
 223     }
 224     
 225     StageStyle getStyle() {
 226         return style;
 227     }
 228 
 229     @Override public TKScene createTKScene(boolean depthBuffer, boolean msaa, AccessControlContext acc) {
 230         ViewScene scene = new ViewScene(depthBuffer, msaa);
 231         scene.setSecurityContext(acc);
 232         return scene;
 233     }
 234     
 235     /**
 236      * Set the scene to be displayed in this stage
 237      *
 238      * @param scene The peer of the scene to be displayed
 239      */
 240     @Override public void setScene(TKScene scene) {
 241         GlassScene oldScene = getScene();
 242         if (oldScene == scene) {
 243             // Nothing to do
 244             return;
 245         }
 246         // RT-21465, RT-28490
 247         // We don't support scene changes in full-screen mode.
 248         exitFullScreen();
 249         super.setScene(scene);
 250         if (scene != null) {
 251             GlassScene newScene = getViewScene();
 252             View view = newScene.getPlatformView();
 253             QuantumToolkit.runWithRenderLock(() -> {
 254                 platformWindow.setView(view);
 255                 if (oldScene != null) oldScene.updateSceneState();
 256                 newScene.updateSceneState();
 257                 return null;
 258             });
 259             requestFocus();
 260         } else {
 261             QuantumToolkit.runWithRenderLock(() -> {
 262                 // platformWindow can be null here, if this window is owned,
 263                 // and its owner is being closed.
 264                 if (platformWindow != null) {
 265                     platformWindow.setView(null);
 266                 }
 267                 if (oldScene != null) {
 268                     oldScene.updateSceneState();
 269                 }
 270                 return null;
 271             });
 272         }
 273         if (oldScene != null) {
 274             ViewPainter painter = ((ViewScene)oldScene).getPainter();
 275             QuantumRenderer.getInstance().disposePresentable(painter.presentable);   // latched on RT
 276         }
 277     }
 278     
 279     @Override public void setBounds(float x, float y, boolean xSet, boolean ySet,
 280                                     float w, float h, float cw, float ch,
 281                                     float xGravity, float yGravity)
 282     {
 283         if (isAppletStage) {
 284             xSet = ySet = false;
 285         }
 286         float pScale = platformWindow.getPlatformScale();
 287         int px, py;
 288         if (xSet || ySet) {
 289             Screen screen = platformWindow.getScreen();
 290             List<Screen> screens = Screen.getScreens();
 291             if (screens.size() > 1) {
 292                 float wx = xSet ? x : platformWindow.getX();
 293                 float wy = ySet ? y : platformWindow.getY();
 294                 float relx = screen.getX() + screen.getWidth() / 2.0f - wx;
 295                 float rely = screen.getY() + screen.getHeight()/ 2.0f - wy;
 296                 float distsq = relx * relx + rely * rely;
 297                 for (Screen s : Screen.getScreens()) {
 298                     relx = s.getX() + s.getWidth() / 2.0f - wx;
 299                     rely = s.getY() + s.getHeight()/ 2.0f - wy;
 300                     float distsq2 = relx * relx + rely * rely;
 301                     if (distsq2 < distsq) {
 302                         screen = s;
 303                         distsq = distsq2;
 304                     }
 305                 }
 306             }
 307             float sx = screen == null ? 0 : screen.getX();
 308             float sy = screen == null ? 0 : screen.getY();
 309             px = xSet ? Math.round(sx + (x - sx) * pScale) : 0;
 310             py = ySet ? Math.round(sy + (y - sy) * pScale) : 0;
 311         } else {
 312             px = py = 0;
 313         }
 314         int pw = (int) (w > 0 ? Math.ceil(w * pScale) : w);
 315         int ph = (int) (h > 0 ? Math.ceil(h * pScale) : h);
 316         int pcw = (int) (cw > 0 ? Math.ceil(cw * pScale) : cw);
 317         int pch = (int) (ch > 0 ? Math.ceil(ch * pScale) : ch);
 318         platformWindow.setBounds(px, py, xSet, ySet, 
 319                                  pw, ph, pcw, pch, 
 320                                  xGravity, yGravity);
 321     }
 322 
 323     @Override
 324     public float getUIScale() {
 325         return platformWindow.getPlatformScale();
 326     }
 327 
 328     @Override
 329     public float getRenderScale() {
 330         return platformWindow.getRenderScale();
 331     }
 332 
 333     @Override public void setMinimumSize(int minWidth, int minHeight) {
 334         platformWindow.setMinimumSize(minWidth, minHeight);
 335     }
 336 
 337     @Override public void setMaximumSize(int maxWidth, int maxHeight) {
 338         platformWindow.setMaximumSize(maxWidth, maxHeight);
 339     }
 340 
 341     static Image findBestImage(java.util.List icons, int width, int height) {
 342         Image image = null;
 343         double bestSimilarity = 3; //Impossibly high value
 344         for (Object icon : icons) {
 345             //Iterate imageList looking for best matching image.
 346             //'Similarity' measure is defined as good scale factor and small insets.
 347             //best possible similarity is 0 (no scale, no insets).
 348             //It's found by experimentation that good-looking results are achieved
 349             //with scale factors x1, x3/4, x2/3, xN, x1/N.
 350             //Check to make sure the image/image format is correct.
 351             Image im = (Image)icon;
 352             if (im == null || !(im.getPixelFormat() == PixelFormat.BYTE_RGB ||
 353                 im.getPixelFormat() == PixelFormat.BYTE_BGRA_PRE ||
 354                 im.getPixelFormat() == PixelFormat.BYTE_GRAY))
 355             {
 356                 continue;
 357             }
 358 
 359             int iw = im.getWidth();
 360             int ih = im.getHeight();
 361 
 362             if (iw > 0 && ih > 0) {
 363                 //Calc scale factor
 364                 double scaleFactor = Math.min((double)width / (double)iw,
 365                                               (double)height / (double)ih);
 366                 //Calculate scaled image dimensions
 367                 //adjusting scale factor to nearest "good" value
 368                 int adjw;
 369                 int adjh;
 370                 double scaleMeasure = 1; //0 - best (no) scale, 1 - impossibly bad
 371                 if (scaleFactor >= 2) {
 372                     //Need to enlarge image more than twice
 373                     //Round down scale factor to multiply by integer value
 374                     scaleFactor = Math.floor(scaleFactor);
 375                     adjw = iw * (int)scaleFactor;
 376                     adjh = ih * (int)scaleFactor;
 377                     scaleMeasure = 1.0 - 0.5 / scaleFactor;
 378                 } else if (scaleFactor >= 1) {
 379                     //Don't scale
 380                     scaleFactor = 1.0;
 381                     adjw = iw;
 382                     adjh = ih;
 383                     scaleMeasure = 0;
 384                 } else if (scaleFactor >= 0.75) {
 385                     //Multiply by 3/4
 386                     scaleFactor = 0.75;
 387                     adjw = iw * 3 / 4;
 388                     adjh = ih * 3 / 4;
 389                     scaleMeasure = 0.3;
 390                 } else if (scaleFactor >= 0.6666) {
 391                     //Multiply by 2/3
 392                     scaleFactor = 0.6666;
 393                     adjw = iw * 2 / 3;
 394                     adjh = ih * 2 / 3;
 395                     scaleMeasure = 0.33;
 396                 } else {
 397                     //Multiply size by 1/scaleDivider
 398                     //where scaleDivider is minimum possible integer
 399                     //larger than 1/scaleFactor
 400                     double scaleDivider = Math.ceil(1.0 / scaleFactor);
 401                     scaleFactor = 1.0 / scaleDivider;
 402                     adjw = (int)Math.round((double)iw / scaleDivider);
 403                     adjh = (int)Math.round((double)ih / scaleDivider);
 404                     scaleMeasure = 1.0 - 1.0 / scaleDivider;
 405                 }
 406                 double similarity = ((double)width - (double)adjw) / (double)width +
 407                     ((double)height - (double)adjh) / (double)height + //Large padding is bad
 408                     scaleMeasure; //Large rescale is bad
 409                 if (similarity < bestSimilarity) {
 410                     bestSimilarity = similarity;
 411                     image = im;
 412                 }
 413                 if (similarity == 0) break;
 414             }
 415         }
 416         return image;
 417     }
 418 
 419     @Override public void setIcons(java.util.List icons) {
 420 
 421         int SMALL_ICON_HEIGHT = 32;
 422         int SMALL_ICON_WIDTH = 32;
 423         if (PlatformUtil.isMac()) { //Mac Sized Icons
 424             SMALL_ICON_HEIGHT = 128;
 425             SMALL_ICON_WIDTH = 128;
 426         } else if (PlatformUtil.isWindows()) { //Windows Sized Icons
 427             SMALL_ICON_HEIGHT = 32;
 428             SMALL_ICON_WIDTH = 32;
 429         } else if (PlatformUtil.isLinux()) { //Linux icons
 430             SMALL_ICON_HEIGHT = 128;
 431             SMALL_ICON_WIDTH = 128;
 432         }
 433 
 434         if (icons == null || icons.size() < 1) { //no icons passed in
 435             platformWindow.setIcon(null);
 436             return;
 437         }
 438 
 439         Image image = findBestImage(icons, SMALL_ICON_WIDTH, SMALL_ICON_HEIGHT);
 440         if (image == null) {
 441             //No images were found, possibly all are broken
 442             return;
 443         }
 444 
 445         PushbroomScaler scaler = ScalerFactory.createScaler(image.getWidth(), image.getHeight(),
 446                                                             image.getBytesPerPixelUnit(),
 447                                                             SMALL_ICON_WIDTH, SMALL_ICON_HEIGHT, true);
 448 
 449         //shrink the image and convert the format to INT_ARGB_PRE
 450         ByteBuffer buf = (ByteBuffer) image.getPixelBuffer();
 451         byte bytes[] = new byte[buf.limit()];
 452 
 453         int iheight = image.getHeight();
 454 
 455         //Iterate through each scanline of the image
 456         //and pass it one at a time to the scaling object
 457         for (int z = 0; z < iheight; z++) {
 458             buf.position(z*image.getScanlineStride());
 459             buf.get(bytes, 0, image.getScanlineStride());
 460             scaler.putSourceScanline(bytes, 0);
 461         }
 462 
 463         buf.rewind();
 464 
 465         final Image img = image.iconify(scaler.getDestination(), SMALL_ICON_WIDTH, SMALL_ICON_HEIGHT);
 466         platformWindow.setIcon(PixelUtils.imageToPixels(img));
 467     }
 468 
 469     @Override public void setTitle(String title) {
 470         platformWindow.setTitle(title);
 471     }
 472 
 473     @Override public void setVisible(final boolean visible) {
 474         // Before setting visible to false on the native window, we unblock
 475         // other windows.
 476         if (!visible) {
 477             removeActiveWindow(this);
 478             if (modality == Modality.WINDOW_MODAL) {
 479                 if (owner != null && owner instanceof WindowStage) {
 480                     ((WindowStage) owner).setEnabled(true);
 481                 }
 482             } else if (modality == Modality.APPLICATION_MODAL) {
 483                 windowsSetEnabled(true);
 484             } else {
 485                 // Note: This method is required to workaround a glass issue
 486                 // mentioned in RT-12607
 487                 // If the hiding stage is unfocusable (i.e. it's a PopupStage),
 488                 // then we don't do this to avoid stealing the focus.
 489                 if (!isPopupStage && owner != null && owner instanceof WindowStage) {
 490                     WindowStage ownerStage = (WindowStage)owner;
 491                     ownerStage.requestToFront();
 492                 }
 493             }
 494         }
 495         QuantumToolkit.runWithRenderLock(() -> {
 496             // platformWindow can be null here, if this window is owned,
 497             // and its owner is being closed.
 498             if (platformWindow != null) {
 499                 platformWindow.setVisible(visible);
 500             }
 501             super.setVisible(visible);
 502             return null;
 503         });
 504         // After setting visible to true on the native window, we block
 505         // other windows.
 506         if (visible) {
 507             if (modality == Modality.WINDOW_MODAL) {
 508                 if (owner != null && owner instanceof WindowStage) {
 509                     ((WindowStage) owner).setEnabled(false);
 510                 }
 511             } else if (modality == Modality.APPLICATION_MODAL) {
 512                 windowsSetEnabled(false);
 513             }
 514             if (isAppletStage && null != appletWindow) {
 515                 appletWindow.assertStageOrder();
 516             }
 517         }
 518         
 519         applyFullScreen();
 520     }
 521     
 522     @Override boolean isVisible() {
 523         return platformWindow.isVisible();
 524     }
 525     
 526     @Override public void setOpacity(float opacity) {
 527         platformWindow.setAlpha(opacity);
 528         GlassScene gs = getScene();
 529         if (gs != null) {
 530             gs.entireSceneNeedsRepaint();
 531         }
 532     }
 533 
 534     public boolean needsUpdateWindow() {
 535         return transparent && (Application.GetApplication().shouldUpdateWindow());
 536     }
 537 
 538     @Override public void setIconified(boolean iconified) {
 539         if (platformWindow.isMinimized() == iconified) {
 540             return;
 541         }
 542         platformWindow.minimize(iconified);
 543     }
 544 
 545     @Override public void setMaximized(boolean maximized) {
 546         if (platformWindow.isMaximized() == maximized) {
 547             return;
 548         }
 549         platformWindow.maximize(maximized);
 550     }
 551 
 552     @Override
 553     public void setAlwaysOnTop(boolean alwaysOnTop) {
 554         // The securityDialog flag takes precedence over alwaysOnTop
 555         if (securityDialog) return;
 556 
 557         if (alwaysOnTop) {
 558             if (hasPermission(alwaysOnTopPermission)) {
 559                 platformWindow.setLevel(Level.FLOATING);
 560             }
 561         } else {
 562             platformWindow.setLevel(Level.NORMAL);
 563         }
 564         
 565     }
 566 
 567     @Override public void setResizable(boolean resizable) {
 568         platformWindow.setResizable(resizable);
 569         // note: for child windows this is ignored and we fail silently
 570     }
 571 
 572     // Return true if this stage is trusted for full screen - doesn't have a
 573     // security manager, or a permission check doesn't result in a security
 574     // exeception.
 575     boolean isTrustedFullScreen() {
 576         return hasPermission(fullScreenPermission);
 577     }
 578 
 579     // Safely exit full screen
 580     void exitFullScreen() {
 581         setFullScreen(false);
 582     }
 583     
 584     boolean isApplet() {
 585         return isPrimaryStage && null != appletWindow;
 586     }
 587 
 588     private boolean hasPermission(Permission perm) {
 589         try {
 590             if (System.getSecurityManager() != null) {
 591                 getAccessControlContext().checkPermission(perm);
 592             }
 593             return true;
 594         } catch (AccessControlException ae) {
 595             return false;
 596         }
 597     }
 598 
 599     // We may need finer-grained permissions in the future, but for
 600     // now AllPermission is good enough to do the job we need, such
 601     // as fullscreen support for signed/unsigned application.
 602     private static final Permission fullScreenPermission = new AllPermission();
 603     private static final Permission alwaysOnTopPermission = new AllPermission();
 604 
 605     private boolean fullScreenFromUserEvent = false;
 606 
 607     private KeyCombination savedFullScreenExitKey = null;
 608 
 609     public final KeyCombination getSavedFullScreenExitKey() {
 610         return savedFullScreenExitKey;
 611     }
 612 
 613     private void applyFullScreen() {
 614         if (platformWindow == null) {
 615             // applyFullScreen() can be called from setVisible(false), while the
 616             // platformWindow has already been destroyed.
 617             return;
 618         }
 619         View v = platformWindow.getView();
 620         if (isVisible() && v != null && v.isInFullscreen() != isInFullScreen) {
 621             if (isInFullScreen) {
 622                 // Check whether app is full screen trusted or flag is set
 623                 // indicating that the fullscreen request came from an input
 624                 // event handler.
 625                 // If not notify the stageListener to reset fullscreen to false.
 626                 final boolean isTrusted = isTrustedFullScreen();
 627                 if (!isTrusted && !fullScreenFromUserEvent) {
 628                     exitFullScreen();
 629                 } else {
 630                     v.enterFullscreen(false, false, false);
 631                     if (warning != null && warning.inWarningTransition()) {
 632                         warning.setView(getViewScene());
 633                     } else {
 634                         boolean showWarning = true;
 635 
 636                         KeyCombination key = null;
 637                         String exitMessage = null;
 638 
 639                         if (isTrusted && (fxStage != null)) {
 640                             // copy the user set definitions for later use.
 641                             key = fxStage.getFullScreenExitKeyCombination();
 642 
 643                             exitMessage = fxStage.getFullScreenExitHint();
 644                         }
 645 
 646                         savedFullScreenExitKey =
 647                                 key == null
 648                                 ? defaultFullScreenExitKeycombo
 649                                 : key;
 650 
 651                         if (
 652                             // the hint is ""
 653                             "".equals(exitMessage) ||
 654                             // if the key is NO_MATCH
 655                             (savedFullScreenExitKey.equals(KeyCombination.NO_MATCH))
 656                                 ) {
 657                             showWarning = false;
 658                         }
 659 
 660                         // the hint is not set, use the key for the message
 661                         if (showWarning && exitMessage == null) {
 662                             if (key == null) {
 663                                 exitMessage = RESOURCES.getString("OverlayWarningESC");
 664                             } else {
 665                                 String f = RESOURCES.getString("OverlayWarningKey");
 666                                 exitMessage = f.format(f, savedFullScreenExitKey.toString());
 667                             }
 668                         }
 669 
 670                         if (showWarning && warning == null) {
 671                             setWarning(new OverlayWarning(getViewScene()));
 672                         }
 673 
 674                         if (showWarning && warning != null) {
 675                             warning.warn(exitMessage);
 676                         }
 677                     }
 678                 }
 679             } else {
 680                 if (warning != null) {
 681                     warning.cancel();
 682                     setWarning(null);
 683                 }
 684                 v.exitFullscreen(false);
 685             }
 686             // Reset flag once we are done process fullscreen
 687             fullScreenFromUserEvent = false;
 688         } else if (!isVisible() && warning != null) {
 689             // if the window is closed - re-open with fresh warning
 690             warning.cancel();
 691             setWarning(null);
 692         }
 693     }
 694 
 695     void setWarning(OverlayWarning newWarning) {
 696         this.warning = newWarning;
 697         getViewScene().synchroniseOverlayWarning();
 698     }
 699 
 700     OverlayWarning getWarning() {
 701         return warning;
 702     }
 703 
 704     @Override public void setFullScreen(boolean fullScreen) {
 705         if (isInFullScreen == fullScreen) {
 706             return;
 707         }
 708 
 709        // Set a flag indicating whether this method was called from
 710         // an input event handler.
 711         if (isInEventHandler()) {
 712             fullScreenFromUserEvent = true;
 713         }
 714 
 715         GlassStage fsWindow = activeFSWindow.get();
 716         if (fullScreen && (fsWindow != null)) {
 717             fsWindow.setFullScreen(false);
 718         }
 719         isInFullScreen = fullScreen;
 720         applyFullScreen();
 721         if (fullScreen) {
 722             activeFSWindow.set(this);
 723         }
 724     }
 725 
 726     void fullscreenChanged(final boolean fs) {
 727         if (!fs) {
 728             if (activeFSWindow.compareAndSet(this, null)) {
 729                 isInFullScreen = false;
 730             }
 731         } else {
 732             isInFullScreen = true;
 733             activeFSWindow.set(this);
 734         }
 735         AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 736             if (stageListener != null) {
 737                 stageListener.changedFullscreen(fs);
 738             }
 739             return null;
 740         }, getAccessControlContext());
 741     }
 742 
 743     @Override public void toBack() {
 744         platformWindow.toBack();
 745         if (isAppletStage && null != appletWindow) {
 746             appletWindow.assertStageOrder();
 747         }
 748     }
 749 
 750     @Override public void toFront() {
 751         platformWindow.requestFocus(); // RT-17836
 752         platformWindow.toFront();
 753         if (isAppletStage && null != appletWindow) {
 754             appletWindow.assertStageOrder();
 755         }
 756     }
 757     
 758     @Override public void close() {
 759         super.close();
 760         QuantumToolkit.runWithRenderLock(() -> {
 761             // prevents closing a closed platform window
 762             if (platformWindow != null) {
 763                 platformWindows.remove(platformWindow);
 764                 platformWindow.close();
 765                 platformWindow = null;
 766             }
 767             GlassScene oldScene = getViewScene();
 768             if (oldScene != null) {
 769                 oldScene.updateSceneState();
 770             }
 771             return null;
 772         });
 773     }
 774 
 775     // setPlatformWindowClosed is only set upon receiving platform window has
 776     // closed notification. This state is necessary to prevent the platform
 777     // window from being closed more than once.
 778     void setPlatformWindowClosed() {
 779         platformWindow = null;
 780     }
 781 
 782     static void addActiveWindow(WindowStage window) {
 783         activeWindows.remove(window);
 784         activeWindows.add(window);
 785     }
 786 
 787     static void removeActiveWindow(WindowStage window) {
 788         activeWindows.remove(window);
 789     }
 790 
 791     final void handleFocusDisabled() {
 792         if (activeWindows.isEmpty()) {
 793             return;
 794         }
 795         WindowStage window = activeWindows.get(activeWindows.size() - 1);
 796         window.setIconified(false);
 797         window.requestToFront();
 798         window.requestFocus();
 799     }
 800 
 801     @Override public boolean grabFocus() {
 802         return platformWindow.grabFocus();
 803     }
 804 
 805     @Override public void ungrabFocus() {
 806         platformWindow.ungrabFocus();
 807     }
 808 
 809     @Override public void requestFocus() {
 810         platformWindow.requestFocus();
 811     }
 812     
 813     @Override public void requestFocus(FocusCause cause) {
 814         switch (cause) {
 815             case TRAVERSED_FORWARD:
 816                 platformWindow.requestFocus(WindowEvent.FOCUS_GAINED_FORWARD);
 817                 break;
 818             case TRAVERSED_BACKWARD:
 819                 platformWindow.requestFocus(WindowEvent.FOCUS_GAINED_BACKWARD);
 820                 break;
 821             case ACTIVATED:
 822                 platformWindow.requestFocus(WindowEvent.FOCUS_GAINED);
 823                 break;
 824             case DEACTIVATED:
 825                 platformWindow.requestFocus(WindowEvent.FOCUS_LOST);
 826                 break;
 827         }
 828     }
 829 
 830     @Override
 831     protected void setPlatformEnabled(boolean enabled) {
 832         super.setPlatformEnabled(enabled);
 833         platformWindow.setEnabled(enabled);
 834         if (enabled) {
 835             // Check if window is really enabled - to handle nested case
 836             if (platformWindow.isEnabled()) {
 837                 requestToFront();
 838             }
 839         } else {
 840             removeActiveWindow(this);
 841         }
 842     }
 843 
 844     void setEnabled(boolean enabled) {
 845         if ((owner != null) && (owner instanceof WindowStage)) {
 846             ((WindowStage) owner).setEnabled(enabled);
 847         }
 848         /*
 849          * RT-17588 - exit if stage is closed from under us as 
 850          *            any further access to the Glass layer 
 851          *            will throw an exception
 852          */
 853         if (enabled && (platformWindow == null || platformWindow.isClosed())) {
 854             return;
 855         }
 856         setPlatformEnabled(enabled);
 857         if (enabled) {
 858             if (isAppletStage && null != appletWindow) {
 859                 appletWindow.assertStageOrder();
 860             }
 861         }
 862     }
 863 
 864     // Note: This method is required to workaround a glass issue mentioned in RT-12607
 865     protected void requestToFront() {
 866         if (platformWindow != null) {
 867             platformWindow.toFront();
 868             platformWindow.requestFocus();
 869         }
 870     }
 871 
 872     public void setInEventHandler(boolean inEventHandler) {
 873         this.inEventHandler = inEventHandler;
 874     }
 875 
 876     public boolean isInEventHandler() {
 877         return inEventHandler;
 878     }
 879 
 880     @Override
 881     public void requestInput(String text, int type, double width, double height, 
 882                         double Mxx, double Mxy, double Mxz, double Mxt,
 883                         double Myx, double Myy, double Myz, double Myt, 
 884                         double Mzx, double Mzy, double Mzz, double Mzt) {
 885         platformWindow.requestInput(text, type, width, height, 
 886                                     Mxx, Mxy, Mxz, Mxt, 
 887                                     Myx, Myy, Myz, Myt, 
 888                                     Mzx, Mzy, Mzz, Mzt);
 889     }
 890 
 891     @Override
 892     public void releaseInput() {
 893         platformWindow.releaseInput();
 894     }
 895 
 896     @Override public void setRTL(boolean b) {
 897         rtl = b;
 898     }
 899 
 900 }