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.AccessController;
  30 import java.security.AllPermission;
  31 import java.security.Permission;
  32 import java.security.PrivilegedAction;
  33 import java.security.AccessControlContext;
  34 import java.util.HashMap;
  35 import java.util.LinkedList;
  36 import java.util.List;
  37 import java.util.Map;
  38 
  39 import javafx.scene.input.KeyCombination;
  40 import javafx.stage.Modality;
  41 import javafx.stage.Stage;
  42 import javafx.stage.StageStyle;
  43 
  44 import com.sun.glass.events.WindowEvent;
  45 import com.sun.glass.ui.*;
  46 import com.sun.glass.ui.Window.Level;
  47 import com.sun.javafx.PlatformUtil;
  48 import com.sun.javafx.iio.common.PushbroomScaler;
  49 import com.sun.javafx.iio.common.ScalerFactory;
  50 import com.sun.javafx.tk.FocusCause;
  51 import com.sun.javafx.tk.TKScene;
  52 import com.sun.javafx.tk.TKStage;
  53 import com.sun.prism.Image;
  54 import com.sun.prism.PixelFormat;
  55 import java.util.Locale;
  56 import java.util.ResourceBundle;
  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 
  77     // A flag to indicate whether a call was generated from
  78     // an input event handler.
  79     private boolean inEventHandler = false;
  80 
  81     // An active window is visible && enabled && focusable.
  82     // The list is maintained in the z-order, so that the last element
  83     // represents the topmost window (or more accurately, the last
  84     // focused window, which we assume is very close to the last topmost one).
  85     private static List<WindowStage> activeWindows = new LinkedList<>();
  86 
  87     private static Map<Window, WindowStage> platformWindows = new HashMap<>();
  88 
  89     private static GlassAppletWindow appletWindow = null;
  90     static void setAppletWindow(GlassAppletWindow aw) {
  91         appletWindow = aw;
  92     }
  93     static GlassAppletWindow getAppletWindow() {
  94         return appletWindow;
  95     }
  96 
  97     private static final Locale LOCALE = Locale.getDefault();
  98     
  99     private static final ResourceBundle RESOURCES =
 100         ResourceBundle.getBundle(WindowStage.class.getPackage().getName() +
 101                                  ".QuantumMessagesBundle", LOCALE);
 102 
 103 
 104     public WindowStage(javafx.stage.Window peerWindow, boolean securityDialog, final StageStyle stageStyle, Modality modality, TKStage owner) {
 105         this.style = stageStyle;
 106         this.owner = (GlassStage)owner;
 107         this.modality = modality;
 108         this.securityDialog = securityDialog;
 109 
 110         if (peerWindow instanceof javafx.stage.Stage) {
 111             fxStage = (Stage)peerWindow;
 112         } else {
 113             fxStage = null;
 114         }
 115 
 116         transparent = stageStyle == StageStyle.TRANSPARENT;
 117         if (owner == null) {
 118             if (this.modality == Modality.WINDOW_MODAL) {
 119                 this.modality = Modality.NONE;
 120             }
 121         }
 122     }
 123 
 124     final void setIsPrimary() {
 125         isPrimaryStage = true;
 126         if (appletWindow != null) {
 127             // this is an embedded applet stage
 128             isAppletStage = true;
 129         }
 130     }
 131 
 132     final void setIsPopup() {
 133         isPopupStage = true;
 134     }
 135 
 136     final boolean isSecurityDialog() {
 137         return securityDialog;
 138     }
 139 
 140     // Called by QuantumToolkit, so we can override initPlatformWindow in subclasses
 141     public final WindowStage init(GlassSystemMenu sysmenu) {
 142         initPlatformWindow();
 143         platformWindow.setEventHandler(new GlassWindowEventHandler(this));
 144         if (sysmenu.isSupported()) {
 145             sysmenu.createMenuBar();
 146             platformWindow.setMenuBar(sysmenu.getMenuBar());
 147         }
 148         return this;
 149     }
 150 
 151     private void initPlatformWindow() {
 152         if (platformWindow == null) {
 153             Application app = Application.GetApplication();
 154             if (isPrimaryStage && (null != appletWindow)) {
 155                 platformWindow = app.createWindow(appletWindow.getGlassWindow().getNativeWindow());
 156             } else {
 157                 Window ownerWindow = null;
 158                 if (owner instanceof WindowStage) {
 159                     ownerWindow = ((WindowStage)owner).platformWindow;
 160                 }
 161                 boolean resizable = false;
 162                 boolean focusable = true;
 163                 int windowMask = rtl ? Window.RIGHT_TO_LEFT : 0;
 164                 if (isPopupStage) { // TODO: make it a stage style?
 165                     windowMask |= Window.POPUP;
 166                     if (style == StageStyle.TRANSPARENT) {
 167                         windowMask |= Window.TRANSPARENT;
 168                     }
 169                     focusable = false;
 170                 } else {
 171                     switch (style) {
 172                         case UNIFIED:
 173                             if (app.supportsUnifiedWindows()) {
 174                                 windowMask |= Window.UNIFIED;
 175                             }
 176                             // fall through
 177                         case DECORATED:
 178                             windowMask |=
 179                                 Window.TITLED | Window.CLOSABLE | 
 180                                 Window.MINIMIZABLE | Window.MAXIMIZABLE;
 181                             if (ownerWindow != null || modality != Modality.NONE) {
 182                                 windowMask &= 
 183                                     ~(Window.MINIMIZABLE | Window.MAXIMIZABLE);
 184                             }
 185                             resizable = true;
 186                             break;
 187                         case UTILITY:
 188                             windowMask |=  Window.TITLED | Window.UTILITY | Window.CLOSABLE;
 189                             break;
 190                         default:
 191                             windowMask |=
 192                                     (transparent ? Window.TRANSPARENT : Window.UNTITLED) | Window.CLOSABLE;
 193                             break;
 194                     }
 195                 }
 196                 platformWindow =
 197                         app.createWindow(ownerWindow, Screen.getMainScreen(), windowMask);
 198                 platformWindow.setResizable(resizable);
 199                 platformWindow.setFocusable(focusable);
 200                 if (securityDialog) {
 201                     platformWindow.setLevel(Window.Level.FLOATING);
 202                 }
 203             }
 204         }
 205         platformWindows.put(platformWindow, this);
 206     }
 207 
 208     final Window getPlatformWindow() {
 209         return platformWindow;
 210     }
 211 
 212     static WindowStage findWindowStage(Window platformWindow) {
 213         return platformWindows.get(platformWindow);
 214     }
 215 
 216     protected GlassStage getOwner() {
 217         return owner;
 218     }
 219     
 220     protected ViewScene getViewScene() {
 221         return (ViewScene)getScene();
 222     }
 223     
 224     StageStyle getStyle() {
 225         return style;
 226     }
 227 
 228     @Override public TKScene createTKScene(boolean depthBuffer, boolean msaa, AccessControlContext acc) {
 229         ViewScene scene = new ViewScene(depthBuffer, msaa);
 230         scene.setSecurityContext(acc);
 231         return scene;
 232     }
 233     
 234     /**
 235      * Set the scene to be displayed in this stage
 236      *
 237      * @param scene The peer of the scene to be displayed
 238      */
 239     @Override public void setScene(TKScene scene) {
 240         GlassScene oldScene = getScene();
 241         if (oldScene == scene) {
 242             // Nothing to do
 243             return;
 244         }
 245         // RT-21465, RT-28490
 246         // We don't support scene changes in full-screen mode.
 247         exitFullScreen();
 248         super.setScene(scene);
 249         if (scene != null) {
 250             GlassScene newScene = getViewScene();
 251             View view = newScene.getPlatformView();
 252             QuantumToolkit.runWithRenderLock(() -> {
 253                 platformWindow.setView(view);
 254                 if (oldScene != null) oldScene.updateSceneState();
 255                 newScene.updateSceneState();
 256                 return null;
 257             });
 258             requestFocus();
 259         } else {
 260             QuantumToolkit.runWithRenderLock(() -> {
 261                 // platformWindow can be null here, if this window is owned,
 262                 // and its owner is being closed.
 263                 if (platformWindow != null) {
 264                     platformWindow.setView(null);
 265                 }
 266                 if (oldScene != null) {
 267                     oldScene.updateSceneState();
 268                 }
 269                 return null;
 270             });
 271         }
 272         if (oldScene != null) {
 273             ViewPainter painter = ((ViewScene)oldScene).getPainter();
 274             QuantumRenderer.getInstance().disposePresentable(painter.presentable);   // latched on RT
 275         }
 276     }
 277     
 278     @Override public void setBounds(float x, float y, boolean xSet, boolean ySet,
 279                                     float w, float h, float cw, float ch,
 280                                     float xGravity, float yGravity)
 281     {
 282         if (isAppletStage) {
 283             xSet = ySet = false;
 284         }
 285         float pScale = platformWindow.getPlatformScale();
 286         int px, py;
 287         if (xSet || ySet) {
 288             Screen screen = platformWindow.getScreen();
 289             List<Screen> screens = Screen.getScreens();
 290             if (screens.size() > 1) {
 291                 float wx = xSet ? x : platformWindow.getX();
 292                 float wy = ySet ? y : platformWindow.getY();
 293                 float relx = screen.getX() + screen.getWidth() / 2.0f - wx;
 294                 float rely = screen.getY() + screen.getHeight()/ 2.0f - wy;
 295                 float distsq = relx * relx + rely * rely;
 296                 for (Screen s : Screen.getScreens()) {
 297                     relx = s.getX() + s.getWidth() / 2.0f - wx;
 298                     rely = s.getY() + s.getHeight()/ 2.0f - wy;
 299                     float distsq2 = relx * relx + rely * rely;
 300                     if (distsq2 < distsq) {
 301                         screen = s;
 302                         distsq = distsq2;
 303                     }
 304                 }
 305             }
 306             float sx = screen == null ? 0 : screen.getX();
 307             float sy = screen == null ? 0 : screen.getY();
 308             px = xSet ? Math.round(sx + (x - sx) * pScale) : 0;
 309             py = ySet ? Math.round(sy + (y - sy) * pScale) : 0;
 310         } else {
 311             px = py = 0;
 312         }
 313         int pw = (int) (w > 0 ? Math.ceil(w * pScale) : w);
 314         int ph = (int) (h > 0 ? Math.ceil(h * pScale) : h);
 315         int pcw = (int) (cw > 0 ? Math.ceil(cw * pScale) : cw);
 316         int pch = (int) (ch > 0 ? Math.ceil(ch * pScale) : ch);
 317         platformWindow.setBounds(px, py, xSet, ySet, 
 318                                  pw, ph, pcw, pch, 
 319                                  xGravity, yGravity);
 320     }
 321 
 322     @Override
 323     public float getUIScale() {
 324         return platformWindow.getPlatformScale();
 325     }
 326 
 327     @Override
 328     public float getRenderScale() {
 329         return platformWindow.getRenderScale();
 330     }
 331 
 332     @Override public void setMinimumSize(int minWidth, int minHeight) {
 333         platformWindow.setMinimumSize(minWidth, minHeight);
 334     }
 335 
 336     @Override public void setMaximumSize(int maxWidth, int maxHeight) {
 337         platformWindow.setMaximumSize(maxWidth, maxHeight);
 338     }
 339 
 340     static Image findBestImage(java.util.List icons, int width, int height) {
 341         Image image = null;
 342         double bestSimilarity = 3; //Impossibly high value
 343         for (Object icon : icons) {
 344             //Iterate imageList looking for best matching image.
 345             //'Similarity' measure is defined as good scale factor and small insets.
 346             //best possible similarity is 0 (no scale, no insets).
 347             //It's found by experimentation that good-looking results are achieved
 348             //with scale factors x1, x3/4, x2/3, xN, x1/N.
 349             //Check to make sure the image/image format is correct.
 350             Image im = (Image)icon;
 351             if (im == null || !(im.getPixelFormat() == PixelFormat.BYTE_RGB ||
 352                 im.getPixelFormat() == PixelFormat.BYTE_BGRA_PRE ||
 353                 im.getPixelFormat() == PixelFormat.BYTE_GRAY))
 354             {
 355                 continue;
 356             }
 357 
 358             int iw = im.getWidth();
 359             int ih = im.getHeight();
 360 
 361             if (iw > 0 && ih > 0) {
 362                 //Calc scale factor
 363                 double scaleFactor = Math.min((double)width / (double)iw,
 364                                               (double)height / (double)ih);
 365                 //Calculate scaled image dimensions
 366                 //adjusting scale factor to nearest "good" value
 367                 int adjw;
 368                 int adjh;
 369                 double scaleMeasure = 1; //0 - best (no) scale, 1 - impossibly bad
 370                 if (scaleFactor >= 2) {
 371                     //Need to enlarge image more than twice
 372                     //Round down scale factor to multiply by integer value
 373                     scaleFactor = Math.floor(scaleFactor);
 374                     adjw = iw * (int)scaleFactor;
 375                     adjh = ih * (int)scaleFactor;
 376                     scaleMeasure = 1.0 - 0.5 / scaleFactor;
 377                 } else if (scaleFactor >= 1) {
 378                     //Don't scale
 379                     scaleFactor = 1.0;
 380                     adjw = iw;
 381                     adjh = ih;
 382                     scaleMeasure = 0;
 383                 } else if (scaleFactor >= 0.75) {
 384                     //Multiply by 3/4
 385                     scaleFactor = 0.75;
 386                     adjw = iw * 3 / 4;
 387                     adjh = ih * 3 / 4;
 388                     scaleMeasure = 0.3;
 389                 } else if (scaleFactor >= 0.6666) {
 390                     //Multiply by 2/3
 391                     scaleFactor = 0.6666;
 392                     adjw = iw * 2 / 3;
 393                     adjh = ih * 2 / 3;
 394                     scaleMeasure = 0.33;
 395                 } else {
 396                     //Multiply size by 1/scaleDivider
 397                     //where scaleDivider is minimum possible integer
 398                     //larger than 1/scaleFactor
 399                     double scaleDivider = Math.ceil(1.0 / scaleFactor);
 400                     scaleFactor = 1.0 / scaleDivider;
 401                     adjw = (int)Math.round((double)iw / scaleDivider);
 402                     adjh = (int)Math.round((double)ih / scaleDivider);
 403                     scaleMeasure = 1.0 - 1.0 / scaleDivider;
 404                 }
 405                 double similarity = ((double)width - (double)adjw) / (double)width +
 406                     ((double)height - (double)adjh) / (double)height + //Large padding is bad
 407                     scaleMeasure; //Large rescale is bad
 408                 if (similarity < bestSimilarity) {
 409                     bestSimilarity = similarity;
 410                     image = im;
 411                 }
 412                 if (similarity == 0) break;
 413             }
 414         }
 415         return image;
 416     }
 417 
 418     @Override public void setIcons(java.util.List icons) {
 419 
 420         int SMALL_ICON_HEIGHT = 32;
 421         int SMALL_ICON_WIDTH = 32;
 422         if (PlatformUtil.isMac()) { //Mac Sized Icons
 423             SMALL_ICON_HEIGHT = 128;
 424             SMALL_ICON_WIDTH = 128;
 425         } else if (PlatformUtil.isWindows()) { //Windows Sized Icons
 426             SMALL_ICON_HEIGHT = 32;
 427             SMALL_ICON_WIDTH = 32;
 428         } else if (PlatformUtil.isLinux()) { //Linux icons
 429             SMALL_ICON_HEIGHT = 128;
 430             SMALL_ICON_WIDTH = 128;
 431         }
 432 
 433         if (icons == null || icons.size() < 1) { //no icons passed in
 434             platformWindow.setIcon(null);
 435             return;
 436         }
 437 
 438         Image image = findBestImage(icons, SMALL_ICON_WIDTH, SMALL_ICON_HEIGHT);
 439         if (image == null) {
 440             //No images were found, possibly all are broken
 441             return;
 442         }
 443 
 444         PushbroomScaler scaler = ScalerFactory.createScaler(image.getWidth(), image.getHeight(),
 445                                                             image.getBytesPerPixelUnit(),
 446                                                             SMALL_ICON_WIDTH, SMALL_ICON_HEIGHT, true);
 447 
 448         //shrink the image and convert the format to INT_ARGB_PRE
 449         ByteBuffer buf = (ByteBuffer) image.getPixelBuffer();
 450         byte bytes[] = new byte[buf.limit()];
 451 
 452         int iheight = image.getHeight();
 453 
 454         //Iterate through each scanline of the image
 455         //and pass it one at a time to the scaling object
 456         for (int z = 0; z < iheight; z++) {
 457             buf.position(z*image.getScanlineStride());
 458             buf.get(bytes, 0, image.getScanlineStride());
 459             scaler.putSourceScanline(bytes, 0);
 460         }
 461 
 462         buf.rewind();
 463 
 464         final Image img = image.iconify(scaler.getDestination(), SMALL_ICON_WIDTH, SMALL_ICON_HEIGHT);
 465         platformWindow.setIcon(PixelUtils.imageToPixels(img));
 466     }
 467 
 468     @Override public void setTitle(String title) {
 469         platformWindow.setTitle(title);
 470     }
 471 
 472     @Override public void setVisible(final boolean visible) {
 473         // Before setting visible to false on the native window, we unblock
 474         // other windows.
 475         if (!visible) {
 476             removeActiveWindow(this);
 477             if (modality == Modality.WINDOW_MODAL) {
 478                 if (owner != null && owner instanceof WindowStage) {
 479                     ((WindowStage) owner).setEnabled(true);
 480                 }
 481             } else if (modality == Modality.APPLICATION_MODAL) {
 482                 windowsSetEnabled(true);
 483             } else {
 484                 // Note: This method is required to workaround a glass issue
 485                 // mentioned in RT-12607
 486                 // If the hiding stage is unfocusable (i.e. it's a PopupStage),
 487                 // then we don't do this to avoid stealing the focus.
 488                 if (!isPopupStage && owner != null && owner instanceof WindowStage) {
 489                     WindowStage ownerStage = (WindowStage)owner;
 490                     ownerStage.requestToFront();
 491                 }
 492             }
 493         }
 494         QuantumToolkit.runWithRenderLock(() -> {
 495             // platformWindow can be null here, if this window is owned,
 496             // and its owner is being closed.
 497             if (platformWindow != null) {
 498                 platformWindow.setVisible(visible);
 499             }
 500             super.setVisible(visible);
 501             return null;
 502         });
 503         // After setting visible to true on the native window, we block
 504         // other windows.
 505         if (visible) {
 506             if (modality == Modality.WINDOW_MODAL) {
 507                 if (owner != null && owner instanceof WindowStage) {
 508                     ((WindowStage) owner).setEnabled(false);
 509                 }
 510             } else if (modality == Modality.APPLICATION_MODAL) {
 511                 windowsSetEnabled(false);
 512             }
 513             if (isAppletStage && null != appletWindow) {
 514                 appletWindow.assertStageOrder();
 515             }
 516         }
 517         
 518         applyFullScreen();
 519     }
 520     
 521     @Override boolean isVisible() {
 522         return platformWindow.isVisible();
 523     }
 524     
 525     @Override public void setOpacity(float opacity) {
 526         platformWindow.setAlpha(opacity);
 527         GlassScene gs = getScene();
 528         if (gs != null) {
 529             gs.entireSceneNeedsRepaint();
 530         }
 531     }
 532 
 533     public boolean needsUpdateWindow() {
 534         return transparent && (Application.GetApplication().shouldUpdateWindow());
 535     }
 536 
 537     @Override public void setIconified(boolean iconified) {
 538         if (platformWindow.isMinimized() == iconified) {
 539             return;
 540         }
 541         platformWindow.minimize(iconified);
 542     }
 543 
 544     @Override public void setMaximized(boolean maximized) {
 545         if (platformWindow.isMaximized() == maximized) {
 546             return;
 547         }
 548         platformWindow.maximize(maximized);
 549     }
 550 
 551     @Override
 552     public void setAlwaysOnTop(boolean alwaysOnTop) {
 553         // The securityDialog flag takes precedence over alwaysOnTop
 554         if (securityDialog) return;
 555 
 556         if (alwaysOnTop) {
 557             if (hasPermission(alwaysOnTopPermission)) {
 558                 platformWindow.setLevel(Level.FLOATING);
 559             }
 560         } else {
 561             platformWindow.setLevel(Level.NORMAL);
 562         }
 563         
 564     }
 565 
 566     @Override public void setResizable(boolean resizable) {
 567         platformWindow.setResizable(resizable);
 568         // note: for child windows this is ignored and we fail silently
 569     }
 570 
 571     // Return true if this stage is trusted for full screen - doesn't have a
 572     // security manager, or a permission check doesn't result in a security
 573     // exeception.
 574     boolean isTrustedFullScreen() {
 575         return hasPermission(fullScreenPermission);
 576     }
 577 
 578     // Safely exit full screen
 579     void exitFullScreen() {
 580         setFullScreen(false);
 581     }
 582     
 583     boolean isApplet() {
 584         return isPrimaryStage && null != appletWindow;
 585     }
 586 
 587     private boolean hasPermission(Permission perm) {
 588         try {
 589             final SecurityManager sm = System.getSecurityManager();
 590             if (sm != null) {
 591                 sm.checkPermission(perm, getAccessControlContext());
 592             }
 593             return true;
 594         } catch (SecurityException se) {
 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 }