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             }
 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                                     float renderScaleX, float renderScaleY)
 283     {
 284         if (renderScaleX > 0.0 || renderScaleY > 0.0) {
 285             // We set the render scale first since the call to setBounds()
 286             // below can induce a recursive update on the scales if it moves
 287             // the window to a new screen and we will then end up being called
 288             // back with a new scale.  We do not want to set these old scale
 289             // values after that recursion happens.
 290             if (renderScaleX > 0.0) {
 291                 platformWindow.setRenderScaleX(renderScaleX);
 292             }
 293             if (renderScaleY > 0.0) {
 294                 platformWindow.setRenderScaleY(renderScaleY);
 295             }
 296             ViewScene vscene = getViewScene();
 297             if (vscene != null) {
 298                 vscene.updateSceneState();
 299                 vscene.entireSceneNeedsRepaint();
 300             }
 301         }
 302         if (isAppletStage) {
 303             xSet = ySet = false;
 304         }
 305         float pScaleX = platformWindow.getPlatformScaleX();
 306         float pScaleY = platformWindow.getPlatformScaleY();
 307         int px, py;
 308         if (xSet || ySet) {
 309             Screen screen = platformWindow.getScreen();
 310             List<Screen> screens = Screen.getScreens();
 311             if (screens.size() > 1) {
 312                 float winfxW = (w > 0) ? w : (platformWindow.getWidth() / pScaleX);
 313                 float winfxH = (h > 0) ? h : (platformWindow.getHeight()/ pScaleY);
 314                 float winfxX = xSet ? x :
 315                         screen.getX() + (platformWindow.getX() - screen.getPlatformX()) / pScaleX;
 316                 float winfxY = ySet ? y :
 317                         screen.getY() + (platformWindow.getY() - screen.getPlatformY()) / pScaleY;
 318                 float winfxCX = winfxX + winfxW/2f;
 319                 float winfxCY = winfxY + winfxH/2f;
 320                 // If the center point of the window (winfxCX,Y) is on any
 321                 // screen, then use that screen.  Otherwise, use the screen
 322                 // whose center is closest to the window center.
 323                 int scrX = screen.getX();
 324                 int scrY = screen.getY();
 325                 int scrW = screen.getWidth();
 326                 int scrH = screen.getHeight();
 327                 if (winfxCX < scrX ||
 328                     winfxCY < scrY ||
 329                     winfxCX >= scrX + scrW ||
 330                     winfxCY >= scrY + scrH)
 331                 {
 332                     float relx = scrX + scrW / 2.0f - winfxCX;
 333                     float rely = scrY + scrH / 2.0f - winfxCY;
 334                     float distsq = relx * relx + rely * rely;
 335                     for (Screen s : Screen.getScreens()) {
 336                         scrX = s.getX();
 337                         scrY = s.getY();
 338                         scrW = s.getWidth();
 339                         scrH = s.getHeight();
 340                         if (winfxCX >= scrX &&
 341                             winfxCY >= scrY &&
 342                             winfxCX < scrX + scrW &&
 343                             winfxCY < scrY + scrH)
 344                         {
 345                             screen = s;
 346                             break;
 347                         }
 348                         relx = scrX + scrW / 2.0f - winfxCX;
 349                         rely = scrY + scrH / 2.0f - winfxCY;
 350                         float distsq2 = relx * relx + rely * rely;
 351                         if (distsq2 < distsq) {
 352                             screen = s;
 353                             distsq = distsq2;
 354                         }
 355                     }
 356                 }
 357             }
 358             float sx = screen == null ? 0 : screen.getX();
 359             float sy = screen == null ? 0 : screen.getY();
 360             float sScaleX = screen.getPlatformScaleX();
 361             float sScaleY = screen.getPlatformScaleY();
 362             px = xSet ? Math.round(screen.getPlatformX() + (x - sx) * sScaleX) : 0;
 363             py = ySet ? Math.round(screen.getPlatformY() + (y - sy) * sScaleY) : 0;
 364         } else {
 365             px = py = 0;
 366         }
 367         if (xSet || ySet || w > 0 || h > 0 || cw > 0 || ch > 0) {
 368             int pw = (int) (w > 0 ? Math.ceil(w * pScaleX) : w);
 369             int ph = (int) (h > 0 ? Math.ceil(h * pScaleY) : h);
 370             int pcw = (int) (cw > 0 ? Math.ceil(cw * pScaleX) : cw);
 371             int pch = (int) (ch > 0 ? Math.ceil(ch * pScaleY) : ch);
 372             platformWindow.setBounds(px, py, xSet, ySet,
 373                                      pw, ph, pcw, pch,
 374                                      xGravity, yGravity);
 375         }
 376     }
 377 
 378     @Override
 379     public float getPlatformScaleX() {
 380         return platformWindow.getPlatformScaleX();
 381     }
 382 
 383     @Override
 384     public float getPlatformScaleY() {
 385         return platformWindow.getPlatformScaleY();
 386     }
 387 
 388     @Override
 389     public float getOutputScaleX() {
 390         return platformWindow.getOutputScaleX();
 391     }
 392 
 393     @Override
 394     public float getOutputScaleY() {
 395         return platformWindow.getOutputScaleY();
 396     }
 397 
 398     @Override public void setMinimumSize(int minWidth, int minHeight) {
 399         minWidth  = (int) Math.ceil(minWidth  * getPlatformScaleX());
 400         minHeight = (int) Math.ceil(minHeight * getPlatformScaleY());
 401         platformWindow.setMinimumSize(minWidth, minHeight);
 402     }
 403 
 404     @Override public void setMaximumSize(int maxWidth, int maxHeight) {
 405         maxWidth  = (int) Math.ceil(maxWidth  * getPlatformScaleX());
 406         maxHeight = (int) Math.ceil(maxHeight * getPlatformScaleY());
 407         platformWindow.setMaximumSize(maxWidth, maxHeight);
 408     }
 409 
 410     static Image findBestImage(java.util.List icons, int width, int height) {
 411         Image image = null;
 412         double bestSimilarity = 3; //Impossibly high value
 413         for (Object icon : icons) {
 414             //Iterate imageList looking for best matching image.
 415             //'Similarity' measure is defined as good scale factor and small insets.
 416             //best possible similarity is 0 (no scale, no insets).
 417             //It's found by experimentation that good-looking results are achieved
 418             //with scale factors x1, x3/4, x2/3, xN, x1/N.
 419             //Check to make sure the image/image format is correct.
 420             Image im = (Image)icon;
 421             if (im == null || !(im.getPixelFormat() == PixelFormat.BYTE_RGB ||
 422                 im.getPixelFormat() == PixelFormat.BYTE_BGRA_PRE ||
 423                 im.getPixelFormat() == PixelFormat.BYTE_GRAY))
 424             {
 425                 continue;
 426             }
 427 
 428             int iw = im.getWidth();
 429             int ih = im.getHeight();
 430 
 431             if (iw > 0 && ih > 0) {
 432                 //Calc scale factor
 433                 double scaleFactor = Math.min((double)width / (double)iw,
 434                                               (double)height / (double)ih);
 435                 //Calculate scaled image dimensions
 436                 //adjusting scale factor to nearest "good" value
 437                 int adjw;
 438                 int adjh;
 439                 double scaleMeasure = 1; //0 - best (no) scale, 1 - impossibly bad
 440                 if (scaleFactor >= 2) {
 441                     //Need to enlarge image more than twice
 442                     //Round down scale factor to multiply by integer value
 443                     scaleFactor = Math.floor(scaleFactor);
 444                     adjw = iw * (int)scaleFactor;
 445                     adjh = ih * (int)scaleFactor;
 446                     scaleMeasure = 1.0 - 0.5 / scaleFactor;
 447                 } else if (scaleFactor >= 1) {
 448                     //Don't scale
 449                     scaleFactor = 1.0;
 450                     adjw = iw;
 451                     adjh = ih;
 452                     scaleMeasure = 0;
 453                 } else if (scaleFactor >= 0.75) {
 454                     //Multiply by 3/4
 455                     scaleFactor = 0.75;
 456                     adjw = iw * 3 / 4;
 457                     adjh = ih * 3 / 4;
 458                     scaleMeasure = 0.3;
 459                 } else if (scaleFactor >= 0.6666) {
 460                     //Multiply by 2/3
 461                     scaleFactor = 0.6666;
 462                     adjw = iw * 2 / 3;
 463                     adjh = ih * 2 / 3;
 464                     scaleMeasure = 0.33;
 465                 } else {
 466                     //Multiply size by 1/scaleDivider
 467                     //where scaleDivider is minimum possible integer
 468                     //larger than 1/scaleFactor
 469                     double scaleDivider = Math.ceil(1.0 / scaleFactor);
 470                     scaleFactor = 1.0 / scaleDivider;
 471                     adjw = (int)Math.round((double)iw / scaleDivider);
 472                     adjh = (int)Math.round((double)ih / scaleDivider);
 473                     scaleMeasure = 1.0 - 1.0 / scaleDivider;
 474                 }
 475                 double similarity = ((double)width - (double)adjw) / (double)width +
 476                     ((double)height - (double)adjh) / (double)height + //Large padding is bad
 477                     scaleMeasure; //Large rescale is bad
 478                 if (similarity < bestSimilarity) {
 479                     bestSimilarity = similarity;
 480                     image = im;
 481                 }
 482                 if (similarity == 0) break;
 483             }
 484         }
 485         return image;
 486     }
 487 
 488     @Override public void setIcons(java.util.List icons) {
 489 
 490         int SMALL_ICON_HEIGHT = 32;
 491         int SMALL_ICON_WIDTH = 32;
 492         if (PlatformUtil.isMac()) { //Mac Sized Icons
 493             SMALL_ICON_HEIGHT = 128;
 494             SMALL_ICON_WIDTH = 128;
 495         } else if (PlatformUtil.isWindows()) { //Windows Sized Icons
 496             SMALL_ICON_HEIGHT = 32;
 497             SMALL_ICON_WIDTH = 32;
 498         } else if (PlatformUtil.isLinux()) { //Linux icons
 499             SMALL_ICON_HEIGHT = 128;
 500             SMALL_ICON_WIDTH = 128;
 501         }
 502 
 503         if (icons == null || icons.size() < 1) { //no icons passed in
 504             platformWindow.setIcon(null);
 505             return;
 506         }
 507 
 508         Image image = findBestImage(icons, SMALL_ICON_WIDTH, SMALL_ICON_HEIGHT);
 509         if (image == null) {
 510             //No images were found, possibly all are broken
 511             return;
 512         }
 513 
 514         PushbroomScaler scaler = ScalerFactory.createScaler(image.getWidth(), image.getHeight(),
 515                                                             image.getBytesPerPixelUnit(),
 516                                                             SMALL_ICON_WIDTH, SMALL_ICON_HEIGHT, true);
 517 
 518         //shrink the image and convert the format to INT_ARGB_PRE
 519         ByteBuffer buf = (ByteBuffer) image.getPixelBuffer();
 520         byte bytes[] = new byte[buf.limit()];
 521 
 522         int iheight = image.getHeight();
 523 
 524         //Iterate through each scanline of the image
 525         //and pass it one at a time to the scaling object
 526         for (int z = 0; z < iheight; z++) {
 527             buf.position(z*image.getScanlineStride());
 528             buf.get(bytes, 0, image.getScanlineStride());
 529             scaler.putSourceScanline(bytes, 0);
 530         }
 531 
 532         buf.rewind();
 533 
 534         final Image img = image.iconify(scaler.getDestination(), SMALL_ICON_WIDTH, SMALL_ICON_HEIGHT);
 535         platformWindow.setIcon(PixelUtils.imageToPixels(img));
 536     }
 537 
 538     @Override public void setTitle(String title) {
 539         platformWindow.setTitle(title);
 540     }
 541 
 542     @Override public void setVisible(final boolean visible) {
 543         // Before setting visible to false on the native window, we unblock
 544         // other windows.
 545         if (!visible) {
 546             removeActiveWindow(this);
 547             if (modality == Modality.WINDOW_MODAL) {
 548                 if (owner != null && owner instanceof WindowStage) {
 549                     ((WindowStage) owner).setEnabled(true);
 550                 }
 551             } else if (modality == Modality.APPLICATION_MODAL) {
 552                 windowsSetEnabled(true);
 553             } else {
 554                 // Note: This method is required to workaround a glass issue
 555                 // mentioned in RT-12607
 556                 // If the hiding stage is unfocusable (i.e. it's a PopupStage),
 557                 // then we don't do this to avoid stealing the focus.
 558                 if (!isPopupStage && owner != null && owner instanceof WindowStage) {
 559                     WindowStage ownerStage = (WindowStage)owner;
 560                     ownerStage.requestToFront();
 561                 }
 562             }
 563         }
 564         QuantumToolkit.runWithRenderLock(() -> {
 565             // platformWindow can be null here, if this window is owned,
 566             // and its owner is being closed.
 567             if (platformWindow != null) {
 568                 platformWindow.setVisible(visible);
 569             }
 570             super.setVisible(visible);
 571             return null;
 572         });
 573         // After setting visible to true on the native window, we block
 574         // other windows.
 575         if (visible) {
 576             if (modality == Modality.WINDOW_MODAL) {
 577                 if (owner != null && owner instanceof WindowStage) {
 578                     ((WindowStage) owner).setEnabled(false);
 579                 }
 580             } else if (modality == Modality.APPLICATION_MODAL) {
 581                 windowsSetEnabled(false);
 582             }
 583             if (isAppletStage && null != appletWindow) {
 584                 appletWindow.assertStageOrder();
 585             }
 586         }
 587 
 588         applyFullScreen();
 589     }
 590 
 591     @Override boolean isVisible() {
 592         return platformWindow.isVisible();
 593     }
 594 
 595     @Override public void setOpacity(float opacity) {
 596         platformWindow.setAlpha(opacity);
 597         GlassScene gs = getScene();
 598         if (gs != null) {
 599             gs.entireSceneNeedsRepaint();
 600         }
 601     }
 602 
 603     public boolean needsUpdateWindow() {
 604         return transparent && (Application.GetApplication().shouldUpdateWindow());
 605     }
 606 
 607     @Override public void setIconified(boolean iconified) {
 608         if (platformWindow.isMinimized() == iconified) {
 609             return;
 610         }
 611         platformWindow.minimize(iconified);
 612     }
 613 
 614     @Override public void setMaximized(boolean maximized) {
 615         if (platformWindow.isMaximized() == maximized) {
 616             return;
 617         }
 618         platformWindow.maximize(maximized);
 619     }
 620 
 621     @Override
 622     public void setAlwaysOnTop(boolean alwaysOnTop) {
 623         // The securityDialog flag takes precedence over alwaysOnTop
 624         if (securityDialog) return;
 625 
 626         if (isAlwaysOnTop == alwaysOnTop) {
 627             return;
 628         }
 629 
 630         if (alwaysOnTop) {
 631             if (hasPermission(SET_WINDOW_ALWAYS_ON_TOP_PERMISSION)) {
 632                 platformWindow.setLevel(Level.FLOATING);
 633             } else {
 634                 alwaysOnTop = false;
 635                 if (stageListener != null) {
 636                     stageListener.changedAlwaysOnTop(alwaysOnTop);
 637                 }
 638             }
 639         } else {
 640             platformWindow.setLevel(Level.NORMAL);
 641         }
 642         isAlwaysOnTop = alwaysOnTop;
 643     }
 644 
 645     @Override public void setResizable(boolean resizable) {
 646         platformWindow.setResizable(resizable);
 647         // note: for child windows this is ignored and we fail silently
 648     }
 649 
 650     // Return true if this stage is trusted for full screen - doesn't have a
 651     // security manager, or a permission check doesn't result in a security
 652     // exeception.
 653     boolean isTrustedFullScreen() {
 654         return hasPermission(UNRESTRICTED_FULL_SCREEN_PERMISSION);
 655     }
 656 
 657     // Safely exit full screen
 658     void exitFullScreen() {
 659         setFullScreen(false);
 660     }
 661 
 662     boolean isApplet() {
 663         return isPrimaryStage && null != appletWindow;
 664     }
 665 
 666     private boolean hasPermission(Permission perm) {
 667         try {
 668             final SecurityManager sm = System.getSecurityManager();
 669             if (sm != null) {
 670                 sm.checkPermission(perm, getAccessControlContext());
 671             }
 672             return true;
 673         } catch (SecurityException se) {
 674             return false;
 675         }
 676     }
 677 
 678     private boolean fullScreenFromUserEvent = false;
 679 
 680     private KeyCombination savedFullScreenExitKey = null;
 681 
 682     public final KeyCombination getSavedFullScreenExitKey() {
 683         return savedFullScreenExitKey;
 684     }
 685 
 686     private void applyFullScreen() {
 687         if (platformWindow == null) {
 688             // applyFullScreen() can be called from setVisible(false), while the
 689             // platformWindow has already been destroyed.
 690             return;
 691         }
 692         View v = platformWindow.getView();
 693         if (isVisible() && v != null && v.isInFullscreen() != isInFullScreen) {
 694             if (isInFullScreen) {
 695                 // Check whether app is full screen trusted or flag is set
 696                 // indicating that the fullscreen request came from an input
 697                 // event handler.
 698                 // If not notify the stageListener to reset fullscreen to false.
 699                 final boolean isTrusted = isTrustedFullScreen();
 700                 if (!isTrusted && !fullScreenFromUserEvent) {
 701                     exitFullScreen();
 702                     fullscreenChanged(false);
 703                 } else {
 704                     v.enterFullscreen(false, false, false);
 705                     if (warning != null && warning.inWarningTransition()) {
 706                         warning.setView(getViewScene());
 707                     } else {
 708                         boolean showWarning = true;
 709 
 710                         KeyCombination key = null;
 711                         String exitMessage = null;
 712 
 713                         if (isTrusted && (fxStage != null)) {
 714                             // copy the user set definitions for later use.
 715                             key = fxStage.getFullScreenExitKeyCombination();
 716 
 717                             exitMessage = fxStage.getFullScreenExitHint();
 718                         }
 719 
 720                         savedFullScreenExitKey =
 721                                 key == null
 722                                 ? defaultFullScreenExitKeycombo
 723                                 : key;
 724 
 725                         if (
 726                             // the hint is ""
 727                             "".equals(exitMessage) ||
 728                             // if the key is NO_MATCH
 729                             (savedFullScreenExitKey.equals(KeyCombination.NO_MATCH))
 730                                 ) {
 731                             showWarning = false;
 732                         }
 733 
 734                         // the hint is not set, use the key for the message
 735                         if (showWarning && exitMessage == null) {
 736                             if (key == null) {
 737                                 exitMessage = RESOURCES.getString("OverlayWarningESC");
 738                             } else {
 739                                 String f = RESOURCES.getString("OverlayWarningKey");
 740                                 exitMessage = f.format(f, savedFullScreenExitKey.toString());
 741                             }
 742                         }
 743 
 744                         if (showWarning && warning == null) {
 745                             setWarning(new OverlayWarning(getViewScene()));
 746                         }
 747 
 748                         if (showWarning && warning != null) {
 749                             warning.warn(exitMessage);
 750                         }
 751                     }
 752                 }
 753             } else {
 754                 if (warning != null) {
 755                     warning.cancel();
 756                     setWarning(null);
 757                 }
 758                 v.exitFullscreen(false);
 759             }
 760             // Reset flag once we are done process fullscreen
 761             fullScreenFromUserEvent = false;
 762         } else if (!isVisible() && warning != null) {
 763             // if the window is closed - re-open with fresh warning
 764             warning.cancel();
 765             setWarning(null);
 766         }
 767     }
 768 
 769     void setWarning(OverlayWarning newWarning) {
 770         this.warning = newWarning;
 771         getViewScene().synchroniseOverlayWarning();
 772     }
 773 
 774     OverlayWarning getWarning() {
 775         return warning;
 776     }
 777 
 778     @Override public void setFullScreen(boolean fullScreen) {
 779         if (isInFullScreen == fullScreen) {
 780             return;
 781         }
 782 
 783        // Set a flag indicating whether this method was called from
 784         // an allowed input event handler.
 785         if (isInAllowedEventHandler()) {
 786             fullScreenFromUserEvent = true;
 787         }
 788 
 789         GlassStage fsWindow = activeFSWindow.get();
 790         if (fullScreen && (fsWindow != null)) {
 791             fsWindow.setFullScreen(false);
 792         }
 793         isInFullScreen = fullScreen;
 794         applyFullScreen();
 795         if (fullScreen) {
 796             activeFSWindow.set(this);
 797         }
 798     }
 799 
 800     void fullscreenChanged(final boolean fs) {
 801         if (!fs) {
 802             if (activeFSWindow.compareAndSet(this, null)) {
 803                 isInFullScreen = false;
 804             }
 805         } else {
 806             isInFullScreen = true;
 807             activeFSWindow.set(this);
 808         }
 809         AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 810             if (stageListener != null) {
 811                 stageListener.changedFullscreen(fs);
 812             }
 813             return null;
 814         }, getAccessControlContext());
 815     }
 816 
 817     @Override public void toBack() {
 818         platformWindow.toBack();
 819         if (isAppletStage && null != appletWindow) {
 820             appletWindow.assertStageOrder();
 821         }
 822     }
 823 
 824     @Override public void toFront() {
 825         platformWindow.requestFocus(); // RT-17836
 826         platformWindow.toFront();
 827         if (isAppletStage && null != appletWindow) {
 828             appletWindow.assertStageOrder();
 829         }
 830     }
 831 
 832     @Override public void close() {
 833         super.close();
 834         QuantumToolkit.runWithRenderLock(() -> {
 835             // prevents closing a closed platform window
 836             if (platformWindow != null) {
 837                 platformWindows.remove(platformWindow);
 838                 platformWindow.close();
 839                 platformWindow = null;
 840             }
 841             GlassScene oldScene = getViewScene();
 842             if (oldScene != null) {
 843                 oldScene.updateSceneState();
 844             }
 845             return null;
 846         });
 847     }
 848 
 849     // setPlatformWindowClosed is only set upon receiving platform window has
 850     // closed notification. This state is necessary to prevent the platform
 851     // window from being closed more than once.
 852     void setPlatformWindowClosed() {
 853         platformWindow = null;
 854     }
 855 
 856     static void addActiveWindow(WindowStage window) {
 857         activeWindows.remove(window);
 858         activeWindows.add(window);
 859     }
 860 
 861     static void removeActiveWindow(WindowStage window) {
 862         activeWindows.remove(window);
 863     }
 864 
 865     final void handleFocusDisabled() {
 866         if (activeWindows.isEmpty()) {
 867             return;
 868         }
 869         WindowStage window = activeWindows.get(activeWindows.size() - 1);
 870         window.setIconified(false);
 871         window.requestToFront();
 872         window.requestFocus();
 873     }
 874 
 875     @Override public boolean grabFocus() {
 876         return platformWindow.grabFocus();
 877     }
 878 
 879     @Override public void ungrabFocus() {
 880         platformWindow.ungrabFocus();
 881     }
 882 
 883     @Override public void requestFocus() {
 884         platformWindow.requestFocus();
 885     }
 886 
 887     @Override public void requestFocus(FocusCause cause) {
 888         switch (cause) {
 889             case TRAVERSED_FORWARD:
 890                 platformWindow.requestFocus(WindowEvent.FOCUS_GAINED_FORWARD);
 891                 break;
 892             case TRAVERSED_BACKWARD:
 893                 platformWindow.requestFocus(WindowEvent.FOCUS_GAINED_BACKWARD);
 894                 break;
 895             case ACTIVATED:
 896                 platformWindow.requestFocus(WindowEvent.FOCUS_GAINED);
 897                 break;
 898             case DEACTIVATED:
 899                 platformWindow.requestFocus(WindowEvent.FOCUS_LOST);
 900                 break;
 901         }
 902     }
 903 
 904     @Override
 905     protected void setPlatformEnabled(boolean enabled) {
 906         super.setPlatformEnabled(enabled);
 907         platformWindow.setEnabled(enabled);
 908         if (enabled) {
 909             // Check if window is really enabled - to handle nested case
 910             if (platformWindow.isEnabled()) {
 911                 requestToFront();
 912             }
 913         } else {
 914             removeActiveWindow(this);
 915         }
 916     }
 917 
 918     void setEnabled(boolean enabled) {
 919         if ((owner != null) && (owner instanceof WindowStage)) {
 920             ((WindowStage) owner).setEnabled(enabled);
 921         }
 922         /*
 923          * RT-17588 - exit if stage is closed from under us as
 924          *            any further access to the Glass layer
 925          *            will throw an exception
 926          */
 927         if (enabled && (platformWindow == null || platformWindow.isClosed())) {
 928             return;
 929         }
 930         setPlatformEnabled(enabled);
 931         if (enabled) {
 932             if (isAppletStage && null != appletWindow) {
 933                 appletWindow.assertStageOrder();
 934             }
 935         }
 936     }
 937 
 938     // Note: This method is required to workaround a glass issue mentioned in RT-12607
 939     protected void requestToFront() {
 940         if (platformWindow != null) {
 941             platformWindow.toFront();
 942             platformWindow.requestFocus();
 943         }
 944     }
 945 
 946     public void setInAllowedEventHandler(boolean inAllowedEventHandler) {
 947         this.inAllowedEventHandler = inAllowedEventHandler;
 948     }
 949 
 950     private boolean isInAllowedEventHandler() {
 951         return inAllowedEventHandler;
 952     }
 953 
 954     @Override
 955     public void requestInput(String text, int type, double width, double height,
 956                         double Mxx, double Mxy, double Mxz, double Mxt,
 957                         double Myx, double Myy, double Myz, double Myt,
 958                         double Mzx, double Mzy, double Mzz, double Mzt) {
 959         platformWindow.requestInput(text, type, width, height,
 960                                     Mxx, Mxy, Mxz, Mxt,
 961                                     Myx, Myy, Myz, Myt,
 962                                     Mzx, Mzy, Mzz, Mzt);
 963     }
 964 
 965     @Override
 966     public void releaseInput() {
 967         platformWindow.releaseInput();
 968     }
 969 
 970     @Override public void setRTL(boolean b) {
 971         rtl = b;
 972     }
 973 
 974 }