Spec-Zone .ru
спецификации, руководства, описания, API
001/*
002 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.  Oracle designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Oracle in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
022 * or visit www.oracle.com if you need additional information or have any
023 * questions.
024 */
025
026package javafx.stage;
027
028import java.security.AllPermission;
029import java.security.AccessControlContext;
030import java.security.AccessController;
031import java.util.Iterator;
032
033import javafx.beans.property.DoubleProperty;
034import javafx.beans.property.DoublePropertyBase;
035import javafx.beans.property.ObjectProperty;
036import javafx.beans.property.ObjectPropertyBase;
037import javafx.beans.property.ReadOnlyBooleanProperty;
038import javafx.beans.property.ReadOnlyBooleanWrapper;
039import javafx.beans.property.ReadOnlyObjectProperty;
040import javafx.beans.property.ReadOnlyObjectWrapper;
041import javafx.beans.property.ReadOnlyDoubleProperty;
042import javafx.beans.property.ReadOnlyDoubleWrapper;
043import javafx.beans.property.SimpleObjectProperty;
044import javafx.event.Event;
045import javafx.event.EventDispatchChain;
046import javafx.event.EventDispatcher;
047import javafx.event.EventHandler;
048import javafx.event.EventTarget;
049import javafx.event.EventType;
050import javafx.geometry.Rectangle2D;
051import javafx.scene.Scene;
052
053import com.sun.javafx.Utils;
054import com.sun.javafx.WeakReferenceQueue;
055import com.sun.javafx.css.StyleManager;
056import com.sun.javafx.stage.WindowEventDispatcher;
057import com.sun.javafx.stage.WindowHelper;
058import com.sun.javafx.stage.WindowPeerListener;
059import com.sun.javafx.tk.TKPulseListener;
060import com.sun.javafx.tk.TKScene;
061import com.sun.javafx.tk.TKStage;
062import com.sun.javafx.tk.Toolkit;
063
064
065/**
066 * <p>
067 *     A top level window within which a scene is hosted, and with which the user
068 *     interacts. A Window might be a {@link Stage}, {@link PopupWindow}, or other
069 *     such top level. A Window is used also for browser plug-in based deployments.
070 * </p>
071 *
072 */
073public class Window implements EventTarget {
074
075    /**
076     * A list of all the currently existing windows. This is only used by SQE for testing.
077     */
078    private static WeakReferenceQueue<Window>windowQueue = new WeakReferenceQueue<Window>();
079
080    static {
081        WindowHelper.setWindowAccessor(
082                new WindowHelper.WindowAccessor() {
083                    /**
084                     * Allow window peer listeners to directly change window
085                     * location and size without changing the xExplicit,
086                     * yExplicit, widthExplicit and heightExplicit values.
087                     */
088                    @Override
089                    public void setLocation(Window window, double x, double y) {
090                        window.x.set(x - window.winTranslateX);
091                        window.y.set(y - window.winTranslateY);
092                    }
093
094                    @Override
095                    public void setSize(Window window,
096                                        double width,
097                                        double height) {
098                        window.width.set(width);
099                        window.height.set(height);
100                    }
101                });
102    }
103
104    /**
105     * Return all Windows
106     *
107     * @return Iterator of all Windows
108     * @treatAsPrivate implementation detail
109     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
110     */
111    @Deprecated
112    public static Iterator<Window> impl_getWindows() {
113        final SecurityManager securityManager = System.getSecurityManager();
114        if (securityManager != null) {
115            securityManager.checkPermission(new AllPermission());
116        }
117
118        return (Iterator<Window>) windowQueue.iterator();
119    }
120
121    private final AccessControlContext acc = AccessController.getContext();
122
123    protected Window() {
124        // necessary for WindowCloseRequestHandler
125        initializeInternalEventDispatcher();
126    }
127
128    /**
129     * The listener that gets called by peer. It's also responsible for
130     * window size/location synchronization with the window peer, which
131     * occurs on every pulse.
132     *
133     * @treatAsPrivate implementation detail
134     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
135     */
136    @Deprecated
137    protected WindowPeerListener peerListener;
138
139    /**
140     * The peer of this Stage. All external access should be
141     * made though getPeer(). Implementors note: Please ensure that this
142     * variable is defined *after* style and *before* the other variables so
143     * that style has been initialized prior to this call, and so that
144     * impl_peer is initialized prior to subsequent initialization.
145     *
146     * @treatAsPrivate implementation detail
147     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
148     */
149    @Deprecated
150    protected TKStage impl_peer;
151
152    private TKBoundsConfigurator peerBoundsConfigurator =
153            new TKBoundsConfigurator();
154
155    /**
156     * Get Stage's peer
157     *
158     * @treatAsPrivate implementation detail
159     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
160     */
161    @Deprecated
162    public TKStage impl_getPeer() {
163        return impl_peer;
164    }
165
166    /**
167     * @treatAsPrivate implementation detail
168     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
169     */
170    @Deprecated
171    public String impl_getMXWindowType() {
172        return getClass().getSimpleName();
173    }
174
175    /**
176     * Set the width and height of this Window to match the size of the content
177     * of this Window's Scene.
178     */
179    public void sizeToScene() {
180        if (getScene() != null && impl_peer != null) {
181            getScene().impl_preferredSize();
182            adjustSize(false);
183        }
184    }
185
186    private void adjustSize(boolean selfSizePriority) {
187        if (getScene() == null) {
188            return;
189        }
190        if (impl_peer != null) {
191            double sceneWidth = getScene().getWidth();
192            double cw = (sceneWidth > 0) ? sceneWidth : -1;
193            double w = -1;
194            if (selfSizePriority && widthExplicit) {
195                w = getWidth();
196            } else if (cw <= 0) {
197                w = widthExplicit ? getWidth() : -1;
198            } else {
199                widthExplicit = false;
200            }
201            double sceneHeight = getScene().getHeight();
202            double ch = (sceneHeight > 0) ? sceneHeight : -1;
203            double h = -1;
204            if (selfSizePriority && heightExplicit) {
205                h = getHeight();
206            } else if (ch <= 0) {
207                h = heightExplicit ? getHeight() : -1;
208            } else {
209                heightExplicit = false;
210            }
211
212            peerBoundsConfigurator.setSize(w, h, cw, ch);
213            applyBounds();
214        }
215    }
216
217    private static final float CENTER_ON_SCREEN_X_FRACTION = 1.0f / 2;
218    private static final float CENTER_ON_SCREEN_Y_FRACTION = 1.0f / 3;
219
220    /**
221     * Sets x and y properties on this Window so that it is centered on the screen.
222     */
223    public void centerOnScreen() {
224        xExplicit = false;
225        yExplicit = false;
226        if (impl_peer != null) {
227            Rectangle2D bounds = getWindowScreen().getVisualBounds();
228            double centerX =
229                    bounds.getMinX() + (bounds.getWidth() - getWidth())
230                                           * CENTER_ON_SCREEN_X_FRACTION;
231            double centerY =
232                    bounds.getMinY() + (bounds.getHeight() - getHeight())
233                                           * CENTER_ON_SCREEN_Y_FRACTION;
234
235            x.set(centerX - winTranslateX);
236            y.set(centerY - winTranslateY);
237            peerBoundsConfigurator.setLocation(centerX, centerY,
238                                               CENTER_ON_SCREEN_X_FRACTION,
239                                               CENTER_ON_SCREEN_Y_FRACTION);
240            applyBounds();
241        }
242    }
243
244    private double winTranslateX;
245    private double winTranslateY;
246
247    final void setWindowTranslate(final double translateX,
248                                  final double translateY) {
249        if (translateX != winTranslateX) {
250            winTranslateX = translateX;
251            peerBoundsConfigurator.setX(getX() + translateX, 0);
252        }
253        if (translateY != winTranslateY) {
254            winTranslateY = translateY;
255            peerBoundsConfigurator.setY(getY() + translateY, 0);
256        }
257    }
258
259    final double getWindowTranslateX() {
260        return winTranslateX;
261    }
262
263    final double getWindowTranslateY() {
264        return winTranslateY;
265    }
266
267    private boolean xExplicit = false;
268    /**
269     * The horizontal location of this {@code Stage} on the screen. Changing
270     * this attribute will move the {@code Stage} horizontally. Changing this
271     * attribute will not visually affect a {@code Stage} while
272     * {@code fullScreen} is true, but will be honored by the {@code Stage} once
273     * {@code fullScreen} becomes false.
274     */
275    private ReadOnlyDoubleWrapper x =
276            new ReadOnlyDoubleWrapper(this, "x", Double.NaN);
277
278    public final void setX(double value) {
279        x.set(value);
280        peerBoundsConfigurator.setX(value + winTranslateX, 0);
281        xExplicit = true;
282    }
283    public final double getX() { return x.get(); }
284    public final ReadOnlyDoubleProperty xProperty() { return x.getReadOnlyProperty(); }
285
286    private boolean yExplicit = false;
287    /**
288     * The vertical location of this {@code Stage} on the screen. Changing this
289     * attribute will move the {@code Stage} vertically. Changing this
290     * attribute will not visually affect a {@code Stage} while
291     * {@code fullScreen} is true, but will be honored by the {@code Stage} once
292     * {@code fullScreen} becomes false.
293     */
294    private ReadOnlyDoubleWrapper y =
295            new ReadOnlyDoubleWrapper(this, "y", Double.NaN);
296
297    public final void setY(double value) {
298        y.set(value);
299        peerBoundsConfigurator.setY(value + winTranslateY, 0);
300        yExplicit = true;
301    }
302    public final double getY() { return y.get(); }
303    public final ReadOnlyDoubleProperty yProperty() { return y.getReadOnlyProperty(); }
304
305    private boolean widthExplicit = false;
306    /**
307     * The width of this {@code Stage}. Changing this attribute will narrow or
308     * widen the width of the {@code Stage}. Changing this
309     * attribute will not visually affect a {@code Stage} while
310     * {@code fullScreen} is true, but will be honored by the {@code Stage} once
311     * {@code fullScreen} becomes false. This value includes any and all
312     * decorations which may be added by the Operating System such as resizable
313     * frame handles. Typical applications will set the {@link javafx.scene.Scene} width
314     * instead.
315     * <p>
316     * The property is read only because it can be changed externally
317     * by the underlying platform and therefore must not be bindable.
318     * </p>
319     */
320    private ReadOnlyDoubleWrapper width =
321            new ReadOnlyDoubleWrapper(this, "width", Double.NaN);
322
323    public final void setWidth(double value) {
324        width.set(value);
325        peerBoundsConfigurator.setWindowWidth(value);
326        widthExplicit = true;
327    }
328    public final double getWidth() { return width.get(); }
329    public final ReadOnlyDoubleProperty widthProperty() { return width.getReadOnlyProperty(); }
330
331    private boolean heightExplicit = false;
332    /**
333     * The height of this {@code Stage}. Changing this attribute will shrink
334     * or heighten the height of the {@code Stage}. Changing this
335     * attribute will not visually affect a {@code Stage} while
336     * {@code fullScreen} is true, but will be honored by the {@code Stage} once
337     * {@code fullScreen} becomes false. This value includes any and all
338     * decorations which may be added by the Operating System such as the title
339     * bar. Typical applications will set the {@link javafx.scene.Scene} height instead.
340     * <p>
341     * The property is read only because it can be changed externally
342     * by the underlying platform and therefore must not be bindable.
343     * </p>
344     */
345    private ReadOnlyDoubleWrapper height =
346            new ReadOnlyDoubleWrapper(this, "height", Double.NaN);
347
348    public final void setHeight(double value) {
349        height.set(value);
350        peerBoundsConfigurator.setWindowHeight(value);
351        heightExplicit = true;
352    }
353    public final double getHeight() { return height.get(); }
354    public final ReadOnlyDoubleProperty heightProperty() { return height.getReadOnlyProperty(); }
355
356    /**
357     * Whether or not this {@code Window} has the keyboard or input focus.
358     * <p>
359     * The property is read only because it can be changed externally
360     * by the underlying platform and therefore must not be bindable.
361     * </p>
362     *
363     * @profile common
364     */
365    private ReadOnlyBooleanWrapper focused = new ReadOnlyBooleanWrapper() {
366        @Override protected void invalidated() {
367            focusChanged(get());
368        }
369
370        @Override
371        public Object getBean() {
372            return Window.this;
373        }
374
375        @Override
376        public String getName() {
377            return "focused";
378        }
379    };
380
381    /**
382     * @treatAsPrivate
383     * @deprecated
384     */
385    @Deprecated
386    public final void setFocused(boolean value) { focused.set(value); }
387
388    /**
389     * Requests that this {@code Window} get the input focus.
390     */
391    public final void requestFocus() {
392        if (impl_peer != null) {
393            impl_peer.requestFocus();
394        }
395    }
396    public final boolean isFocused() { return focused.get(); }
397    public final ReadOnlyBooleanProperty focusedProperty() { return focused.getReadOnlyProperty(); }
398
399    /**
400     * The {@code Scene} to be rendered on this {@code Stage}. There can only
401     * be one {@code Scene} on the {@code Stage} at a time, and a {@code Scene}
402     * can only be on one {@code Stage} at a time. Setting a {@code Scene} on
403     * a different {@code Stage} will cause the old {@code Stage} to lose the
404     * reference before the new one gains it. You may swap {@code Scene}s on
405     * a {@code Stage} at any time, even while in full-screen exclusive mode.
406     *
407     * An {@link IllegalStateException} is thrown if this property is set
408     * on a thread other than the JavaFX Application Thread.
409     *
410     * @defaultValue null
411     */
412    private SceneModel scene = new SceneModel();
413    protected void setScene(Scene value) { scene.set(value); }
414    public final Scene getScene() { return scene.get(); }
415    public final ReadOnlyObjectProperty<Scene> sceneProperty() { return scene.getReadOnlyProperty(); }
416
417    private final class SceneModel extends ReadOnlyObjectWrapper<Scene> {
418        private Scene oldScene;
419
420        @Override protected void invalidated() {
421            final Scene newScene = get();
422            if (oldScene == newScene) {
423                return;
424            }
425            Toolkit.getToolkit().checkFxUserThread();
426            // First, detach scene peer from this window
427            updatePeerScene(null);
428            // Second, dispose scene peer
429            if (oldScene != null) {
430                oldScene.impl_setWindow(null);
431                StyleManager.getInstance().forget(oldScene);
432            }
433            if (newScene != null) {
434                final Window oldWindow = newScene.getWindow();
435                if (oldWindow != null) {
436                    // if the new scene was previously set to a window
437                    // we need to remove it from that window
438                    // NOTE: can this "scene" property be bound?
439                    oldWindow.setScene(null);
440                }
441
442                // Set the "window" on the new scene. This will also trigger
443                // scene's peer creation.
444                newScene.impl_setWindow(Window.this);
445                // Set scene impl on stage impl
446                updatePeerScene(newScene.impl_getPeer());
447
448                // Fix for RT-15432: we should update new Scene's stylesheets, if the
449                // window is already showing. For not yet shown windows, the update is
450                // performed in Window.visibleChanging()
451                if (isShowing()) {
452                    newScene.getRoot().impl_reapplyCSS();
453
454                    if (!widthExplicit || !heightExplicit) {
455                        getScene().impl_preferredSize();
456                        adjustSize(true);
457                    }
458                }
459            }
460
461            oldScene = newScene;
462        }
463
464        @Override
465        public Object getBean() {
466            return Window.this;
467        }
468
469        @Override
470        public String getName() {
471            return "scene";
472        }
473
474        private void updatePeerScene(final TKScene tkScene) {
475            if (impl_peer != null) {
476                // Set scene impl on stage impl
477                impl_peer.setScene(tkScene);
478            }
479        }
480    }
481
482    /**
483     * Defines the opacity of the {@code Stage} as a value between 0.0 and 1.0.
484     * The opacity is reflected across the {@code Stage}, its {@code Decoration}
485     * and its {@code Scene} content. On a JavaFX runtime platform that does not
486     * support opacity, assigning a value to this variable will have no
487     * visible difference. A {@code Stage} with 0% opacity is fully translucent.
488     * Typically, a {@code Stage} with 0% opacity will not receive any mouse
489     * events.
490     *
491     * @defaultValue 1.0
492     */
493    private DoubleProperty opacity;
494
495    public final void setOpacity(double value) {
496        opacityProperty().set(value);
497    }
498
499    public final double getOpacity() {
500        return opacity == null ? 1.0 : opacity.get();
501    }
502
503    public final DoubleProperty opacityProperty() {
504        if (opacity == null) {
505            opacity = new DoublePropertyBase(1.0) {
506
507                @Override
508                protected void invalidated() {
509                    if (impl_peer != null) {
510                        impl_peer.setOpacity((float) get());
511                    }
512                }
513
514                @Override
515                public Object getBean() {
516                    return Window.this;
517                }
518
519                @Override
520                public String getName() {
521                    return "opacity";
522                }
523            };
524        }
525        return opacity;
526    }
527
528    /**
529     * Called when there is an external request to close this {@code Window}.
530     * The installed event handler can prevent window closing by consuming the
531     * received event.
532     */
533    private ObjectProperty<EventHandler<WindowEvent>> onCloseRequest;
534    public final void setOnCloseRequest(EventHandler<WindowEvent> value) {
535        onCloseRequestProperty().set(value);
536    }
537    public final EventHandler<WindowEvent> getOnCloseRequest() {
538        return (onCloseRequest != null) ? onCloseRequest.get() : null;
539    }
540    public final ObjectProperty<EventHandler<WindowEvent>>
541            onCloseRequestProperty() {
542        if (onCloseRequest == null) {
543            onCloseRequest = new ObjectPropertyBase<EventHandler<WindowEvent>>() {
544                @Override protected void invalidated() {
545                    setEventHandler(WindowEvent.WINDOW_CLOSE_REQUEST, get());
546                }
547
548                @Override
549                public Object getBean() {
550                    return Window.this;
551                }
552
553                @Override
554                public String getName() {
555                    return "onCloseRequest";
556                }
557            };
558        }
559        return onCloseRequest;
560    }
561
562    /**
563     * Called just prior to the Window being shown.
564     */
565    private ObjectProperty<EventHandler<WindowEvent>> onShowing;
566    public final void setOnShowing(EventHandler<WindowEvent> value) { onShowingProperty().set(value); }
567    public final EventHandler<WindowEvent> getOnShowing() {
568        return onShowing == null ? null : onShowing.get();
569    }
570    public final ObjectProperty<EventHandler<WindowEvent>> onShowingProperty() {
571        if (onShowing == null) {
572            onShowing = new ObjectPropertyBase<EventHandler<WindowEvent>>() {
573                @Override protected void invalidated() {
574                    setEventHandler(WindowEvent.WINDOW_SHOWING, get());
575                }
576
577                @Override
578                public Object getBean() {
579                    return Window.this;
580                }
581
582                @Override
583                public String getName() {
584                    return "onShowing";
585                }
586            };
587        }
588        return onShowing;
589    }
590
591    /**
592     * Called just after the Window is shown.
593     */
594    private ObjectProperty<EventHandler<WindowEvent>> onShown;
595    public final void setOnShown(EventHandler<WindowEvent> value) { onShownProperty().set(value); }
596    public final EventHandler<WindowEvent> getOnShown() {
597        return onShown == null ? null : onShown.get();
598    }
599    public final ObjectProperty<EventHandler<WindowEvent>> onShownProperty() {
600        if (onShown == null) {
601            onShown = new ObjectPropertyBase<EventHandler<WindowEvent>>() {
602                @Override protected void invalidated() {
603                    setEventHandler(WindowEvent.WINDOW_SHOWN, get());
604                }
605
606                @Override
607                public Object getBean() {
608                    return Window.this;
609                }
610
611                @Override
612                public String getName() {
613                    return "onShown";
614                }
615            };
616        }
617        return onShown;
618    }
619
620    /**
621     * Called just prior to the Window being hidden.
622     */
623    private ObjectProperty<EventHandler<WindowEvent>> onHiding;
624    public final void setOnHiding(EventHandler<WindowEvent> value) { onHidingProperty().set(value); }
625    public final EventHandler<WindowEvent> getOnHiding() {
626        return onHiding == null ? null : onHiding.get();
627    }
628    public final ObjectProperty<EventHandler<WindowEvent>> onHidingProperty() {
629        if (onHiding == null) {
630            onHiding = new ObjectPropertyBase<EventHandler<WindowEvent>>() {
631                @Override protected void invalidated() {
632                    setEventHandler(WindowEvent.WINDOW_HIDING, get());
633                }
634
635                @Override
636                public Object getBean() {
637                    return Window.this;
638                }
639
640                @Override
641                public String getName() {
642                    return "onHiding";
643                }
644            };
645        }
646        return onHiding;
647    }
648
649    /**
650     * Called just after the Window has been hidden.
651     * When the {@code Window} is hidden, this event handler is invoked allowing
652     * the developer to clean up resources or perform other tasks when the
653     * {@link Window} is closed.
654     */
655    private ObjectProperty<EventHandler<WindowEvent>> onHidden;
656    public final void setOnHidden(EventHandler<WindowEvent> value) { onHiddenProperty().set(value); }
657    public final EventHandler<WindowEvent> getOnHidden() {
658        return onHidden == null ? null : onHidden.get();
659    }
660    public final ObjectProperty<EventHandler<WindowEvent>> onHiddenProperty() {
661        if (onHidden == null) {
662            onHidden = new ObjectPropertyBase<EventHandler<WindowEvent>>() {
663                @Override protected void invalidated() {
664                    setEventHandler(WindowEvent.WINDOW_HIDDEN, get());
665                }
666
667                @Override
668                public Object getBean() {
669                    return Window.this;
670                }
671
672                @Override
673                public String getName() {
674                    return "onHidden";
675                }
676            };
677        }
678        return onHidden;
679    }
680
681    /**
682     * Whether or not this {@code Stage} is showing (that is, open on the
683     * user's system). The Stage might be "showing", yet the user might not
684     * be able to see it due to the Stage being rendered behind another window
685     * or due to the Stage being positioned off the monitor.
686     *
687     * @defaultValue false
688     */
689    private ReadOnlyBooleanWrapper showing = new ReadOnlyBooleanWrapper() {
690        private boolean oldVisible;
691
692        @Override protected void invalidated() {
693            final boolean newVisible = get();
694            if (oldVisible == newVisible) {
695                return;
696            }
697
698            if (!oldVisible && newVisible) {
699                fireEvent(new WindowEvent(Window.this, WindowEvent.WINDOW_SHOWING));
700            } else {
701                fireEvent(new WindowEvent(Window.this, WindowEvent.WINDOW_HIDING));
702            }
703
704            oldVisible = newVisible;
705            impl_visibleChanging(newVisible);
706            if (newVisible) {
707                hasBeenVisible = true;
708                windowQueue.add(Window.this);
709            } else {
710                windowQueue.remove(Window.this);
711            }
712            Toolkit tk = Toolkit.getToolkit();
713            if (impl_peer != null) {
714                if (newVisible) {
715                    impl_peer.setSecurityContext(acc);
716
717                    if (peerListener == null) {
718                        peerListener = new WindowPeerListener(Window.this);
719                    }
720
721                    // Setup listener for changes coming back from peer
722                    impl_peer.setTKStageListener(peerListener);
723                    // Register pulse listener
724                    tk.addStageTkPulseListener(peerBoundsConfigurator);
725
726                    if (getScene() != null) {
727                        getScene().impl_initPeer();
728                        impl_peer.setScene(getScene().impl_getPeer());
729                        getScene().impl_preferredSize();
730                    }
731
732                    // Set peer bounds
733                    if ((getScene() != null) && (!widthExplicit || !heightExplicit)) {
734                        adjustSize(true);
735                    } else {
736                        peerBoundsConfigurator.setSize(
737                                getWidth(), getHeight(), -1, -1);
738                    }
739
740                    if (!xExplicit && !yExplicit) {
741                        centerOnScreen();
742                    } else {
743                        peerBoundsConfigurator.setLocation(
744                                getX() + winTranslateX,
745                                getY() + winTranslateY,
746                                0, 0);
747                    }
748
749                    // set peer bounds before the window is shown
750                    applyBounds();
751
752                    impl_peer.setOpacity((float)getOpacity());
753
754                    impl_peer.setVisible(true);
755                    fireEvent(new WindowEvent(Window.this, WindowEvent.WINDOW_SHOWN));
756                } else {
757                    impl_peer.setVisible(false);
758
759                    // Call listener
760                    fireEvent(new WindowEvent(Window.this, WindowEvent.WINDOW_HIDDEN));
761
762                    if (getScene() != null) {
763                        impl_peer.setScene(null);
764                        getScene().impl_disposePeer();
765                        StyleManager.getInstance().forget(getScene());
766                    }
767
768                    // Remove toolkit pulse listener
769                    tk.removeStageTkPulseListener(peerBoundsConfigurator);
770                    // Remove listener for changes coming back from peer
771                    impl_peer.setTKStageListener(null);
772
773                    // Notify peer
774                    impl_peer.close();
775                }
776            }
777            if (newVisible) {
778                tk.requestNextPulse();
779            }
780            impl_visibleChanged(newVisible);
781        }
782
783        @Override
784        public Object getBean() {
785            return Window.this;
786        }
787
788        @Override
789        public String getName() {
790            return "showing";
791        }
792    };
793    private void setShowing(boolean value) {
794        Toolkit.getToolkit().checkFxUserThread();
795        showing.set(value);
796    }
797    public final boolean isShowing() { return showing.get(); }
798    public final ReadOnlyBooleanProperty showingProperty() { return showing.getReadOnlyProperty(); }
799
800    // flag indicating whether this window has ever been made visible.
801    boolean hasBeenVisible = false;
802
803    /**
804     * Attempts to show this Window by setting visibility to true
805     *
806     * @throws IllegalStateException if this method is called on a thread
807     * other than the JavaFX Application Thread.
808     */
809    protected void show() {
810        setShowing(true);
811    }
812
813    /**
814     * Attempts to hide this Window by setting the visibility to false.
815     *
816     * @throws IllegalStateException if this method is called on a thread
817     * other than the JavaFX Application Thread.
818     */
819    public void hide() {
820        setShowing(false);
821    }
822
823    /**
824     * This can be replaced by listening for the onShowing/onHiding events
825     * @treatAsPrivate implementation detail
826     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
827     */
828    @Deprecated
829    protected void impl_visibleChanging(boolean visible) {
830        if (visible && (getScene() != null)) {
831            getScene().getRoot().impl_reapplyCSS();
832        }
833    }
834
835    /**
836     * This can be replaced by listening for the onShown/onHidden events
837     * @treatAsPrivate implementation detail
838     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
839     */
840    @Deprecated
841    protected void impl_visibleChanged(boolean visible) {
842        assert impl_peer != null;
843        if (!visible) {
844            peerListener = null;
845            impl_peer = null;
846        }
847    }
848
849    // PENDING_DOC_REVIEW
850    /**
851     * Specifies the event dispatcher for this node. The default event
852     * dispatcher sends the received events to the registered event handlers and
853     * filters. When replacing the value with a new {@code EventDispatcher},
854     * the new dispatcher should forward events to the replaced dispatcher
855     * to maintain the node's default event handling behavior.
856     */
857    private ObjectProperty<EventDispatcher> eventDispatcher;
858
859    public final void setEventDispatcher(EventDispatcher value) {
860        eventDispatcherProperty().set(value);
861    }
862
863    public final EventDispatcher getEventDispatcher() {
864        return eventDispatcherProperty().get();
865    }
866
867    public final ObjectProperty<EventDispatcher> eventDispatcherProperty() {
868        initializeInternalEventDispatcher();
869        return eventDispatcher;
870    }
871
872    private WindowEventDispatcher internalEventDispatcher;
873
874    // PENDING_DOC_REVIEW
875    /**
876     * Registers an event handler to this node. The handler is called when the
877     * node receives an {@code Event} of the specified type during the bubbling
878     * phase of event delivery.
879     *
880     * @param <T> the specific event class of the handler
881     * @param eventType the type of the events to receive by the handler
882     * @param eventHandler the handler to register
883     * @throws NullPointerException if the event type or handler is null
884     */
885    public final <T extends Event> void addEventHandler(
886            final EventType<T> eventType,
887            final EventHandler<? super T> eventHandler) {
888        getInternalEventDispatcher().getEventHandlerManager()
889                                    .addEventHandler(eventType, eventHandler);
890    }
891
892    // PENDING_DOC_REVIEW
893    /**
894     * Unregisters a previously registered event handler from this node. One
895     * handler might have been registered for different event types, so the
896     * caller needs to specify the particular event type from which to
897     * unregister the handler.
898     *
899     * @param <T> the specific event class of the handler
900     * @param eventType the event type from which to unregister
901     * @param eventHandler the handler to unregister
902     * @throws NullPointerException if the event type or handler is null
903     */
904    public final <T extends Event> void removeEventHandler(
905            final EventType<T> eventType,
906            final EventHandler<? super T> eventHandler) {
907        getInternalEventDispatcher().getEventHandlerManager()
908                                    .removeEventHandler(eventType,
909                                                        eventHandler);
910    }
911
912    // PENDING_DOC_REVIEW
913    /**
914     * Registers an event filter to this node. The filter is called when the
915     * node receives an {@code Event} of the specified type during the capturing
916     * phase of event delivery.
917     *
918     * @param <T> the specific event class of the filter
919     * @param eventType the type of the events to receive by the filter
920     * @param eventFilter the filter to register
921     * @throws NullPointerException if the event type or filter is null
922     */
923    public final <T extends Event> void addEventFilter(
924            final EventType<T> eventType,
925            final EventHandler<? super T> eventFilter) {
926        getInternalEventDispatcher().getEventHandlerManager()
927                                    .addEventFilter(eventType, eventFilter);
928    }
929
930    // PENDING_DOC_REVIEW
931    /**
932     * Unregisters a previously registered event filter from this node. One
933     * filter might have been registered for different event types, so the
934     * caller needs to specify the particular event type from which to
935     * unregister the filter.
936     *
937     * @param <T> the specific event class of the filter
938     * @param eventType the event type from which to unregister
939     * @param eventFilter the filter to unregister
940     * @throws NullPointerException if the event type or filter is null
941     */
942    public final <T extends Event> void removeEventFilter(
943            final EventType<T> eventType,
944            final EventHandler<? super T> eventFilter) {
945        getInternalEventDispatcher().getEventHandlerManager()
946                                    .removeEventFilter(eventType, eventFilter);
947    }
948
949    /**
950     * Sets the handler to use for this event type. There can only be one such handler
951     * specified at a time. This handler is guaranteed to be called first. This is
952     * used for registering the user-defined onFoo event handlers.
953     *
954     * @param <T> the specific event class of the handler
955     * @param eventType the event type to associate with the given eventHandler
956     * @param eventHandler the handler to register, or null to unregister
957     * @throws NullPointerException if the event type is null
958     */
959    protected final <T extends Event> void setEventHandler(
960            final EventType<T> eventType,
961            final EventHandler<? super T> eventHandler) {
962        getInternalEventDispatcher().getEventHandlerManager()
963                                    .setEventHandler(eventType, eventHandler);
964    }
965
966    WindowEventDispatcher getInternalEventDispatcher() {
967        initializeInternalEventDispatcher();
968        return internalEventDispatcher;
969    }
970
971    private void initializeInternalEventDispatcher() {
972        if (internalEventDispatcher == null) {
973            internalEventDispatcher = createInternalEventDispatcher();
974            eventDispatcher = new SimpleObjectProperty<EventDispatcher>(
975                                          this,
976                                          "eventDispatcher",
977                                          internalEventDispatcher);
978        }
979    }
980
981    WindowEventDispatcher createInternalEventDispatcher() {
982        return new WindowEventDispatcher(this);
983    }
984
985    /**
986     * Fires the specified event.
987     * <p>
988     * This method must be called on the FX user thread.
989     *
990     * @param event the event to fire
991     */
992    public final void fireEvent(Event event) {
993        Event.fireEvent(this, event);
994    }
995
996    // PENDING_DOC_REVIEW
997    /**
998     * Construct an event dispatch chain for this window.
999     *
1000     * @param tail the initial chain to build from
1001     * @return the resulting event dispatch chain for this window
1002     */
1003    @Override
1004    public EventDispatchChain buildEventDispatchChain(
1005            EventDispatchChain tail) {
1006        if (eventDispatcher != null) {
1007            final EventDispatcher eventDispatcherValue = eventDispatcher.get();
1008            if (eventDispatcherValue != null) {
1009                tail = tail.prepend(eventDispatcherValue);
1010            }
1011        }
1012
1013        return tail;
1014    }
1015
1016    private int focusGrabCounter;
1017
1018    void increaseFocusGrabCounter() {
1019        if ((++focusGrabCounter == 1) && (impl_peer != null) && isFocused()) {
1020            impl_peer.grabFocus();
1021        }
1022    }
1023
1024    void decreaseFocusGrabCounter() {
1025        if ((--focusGrabCounter == 0) && (impl_peer != null)) {
1026            impl_peer.ungrabFocus();
1027        }
1028    }
1029
1030    private void focusChanged(final boolean newIsFocused) {
1031        if ((focusGrabCounter > 0) && (impl_peer != null) && newIsFocused) {
1032            impl_peer.grabFocus();
1033        }
1034    }
1035
1036    final void applyBounds() {
1037        peerBoundsConfigurator.apply();
1038    }
1039
1040    Window getWindowOwner() {
1041        return null;
1042    }
1043
1044    private Screen getWindowScreen() {
1045        Window window = this;
1046        do {
1047            if (!Double.isNaN(window.getX())
1048                    && !Double.isNaN(window.getY())
1049                    && !Double.isNaN(window.getWidth())
1050                    && !Double.isNaN(window.getHeight())) {
1051                return Utils.getScreenForRectangle(
1052                                     new Rectangle2D(window.getX(),
1053                                                     window.getY(),
1054                                                     window.getWidth(),
1055                                                     window.getHeight()));
1056            }
1057
1058            window = window.getWindowOwner();
1059        } while (window != null);
1060
1061        return Screen.getPrimary();
1062    }
1063
1064    /**
1065     * Caches all requested bounds settings and applies them at once during
1066     * the next pulse.
1067     */
1068    private final class TKBoundsConfigurator implements TKPulseListener {
1069        private double x;
1070        private double y;
1071        private float xGravity;
1072        private float yGravity;
1073        private double windowWidth;
1074        private double windowHeight;
1075        private double clientWidth;
1076        private double clientHeight;
1077
1078        private boolean dirty;
1079
1080        public TKBoundsConfigurator() {
1081            reset();
1082        }
1083
1084        public void setX(final double x, final float xGravity) {
1085            this.x = x;
1086            this.xGravity = xGravity;
1087            setDirty();
1088        }
1089
1090        public void setY(final double y, final float yGravity) {
1091            this.y = y;
1092            this.yGravity = yGravity;
1093            setDirty();
1094        }
1095
1096        public void setWindowWidth(final double windowWidth) {
1097            this.windowWidth = windowWidth;
1098            setDirty();
1099        }
1100
1101        public void setWindowHeight(final double windowHeight) {
1102            this.windowHeight = windowHeight;
1103            setDirty();
1104        }
1105
1106        public void setClientWidth(final double clientWidth) {
1107            this.clientWidth = clientWidth;
1108            setDirty();
1109        }
1110
1111        public void setClientHeight(final double clientHeight) {
1112            this.clientHeight = clientHeight;
1113            setDirty();
1114        }
1115
1116        public void setLocation(final double x,
1117                                final double y,
1118                                final float xGravity,
1119                                final float yGravity) {
1120            this.x = x;
1121            this.y = y;
1122            this.xGravity = xGravity;
1123            this.yGravity = yGravity;
1124            setDirty();
1125        }
1126
1127        public void setSize(final double windowWidth,
1128                            final double windowHeight,
1129                            final double clientWidth,
1130                            final double clientHeight) {
1131            this.windowWidth = windowWidth;
1132            this.windowHeight = windowHeight;
1133            this.clientWidth = clientWidth;
1134            this.clientHeight = clientHeight;
1135            setDirty();
1136        }
1137
1138        public void apply() {
1139            if (dirty) {
1140                impl_peer.setBounds((float) (Double.isNaN(x) ? 0 : x),
1141                                    (float) (Double.isNaN(y) ? 0 : y),
1142                                    !Double.isNaN(x),
1143                                    !Double.isNaN(y),
1144                                    (float) windowWidth,
1145                                    (float) windowHeight,
1146                                    (float) clientWidth,
1147                                    (float) clientHeight,
1148                                    xGravity, yGravity);
1149
1150                reset();
1151            }
1152        }
1153
1154        @Override
1155        public void pulse() {
1156            apply();
1157        }
1158
1159        private void reset() {
1160            x = Double.NaN;
1161            y = Double.NaN;
1162            xGravity = 0;
1163            yGravity = 0;
1164            windowWidth = -1;
1165            windowHeight = -1;
1166            clientWidth = -1;
1167            clientHeight = -1;
1168            dirty = false;
1169        }
1170
1171        private void setDirty() {
1172            if (!dirty) {
1173                Toolkit.getToolkit().requestNextPulse();
1174                dirty = true;
1175            }
1176        }
1177    }
1178}