1 /*
   2  * Copyright (c) 2012, 2014, Oracle and/or its affiliates.
   3  * All rights reserved. Use is subject to license terms.
   4  *
   5  * This file is available and licensed under the following license:
   6  *
   7  * Redistribution and use in source and binary forms, with or without
   8  * modification, are permitted provided that the following conditions
   9  * are met:
  10  *
  11  *  - Redistributions of source code must retain the above copyright
  12  *    notice, this list of conditions and the following disclaimer.
  13  *  - Redistributions in binary form must reproduce the above copyright
  14  *    notice, this list of conditions and the following disclaimer in
  15  *    the documentation and/or other materials provided with the distribution.
  16  *  - Neither the name of Oracle Corporation nor the names of its
  17  *    contributors may be used to endorse or promote products derived
  18  *    from this software without specific prior written permission.
  19  *
  20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31  */
  32 package com.oracle.javafx.scenebuilder.kit.editor.panel.util;
  33 
  34 import javafx.application.Platform;
  35 import javafx.beans.value.ChangeListener;
  36 import javafx.event.EventHandler;
  37 import javafx.geometry.Bounds;
  38 import javafx.scene.Node;
  39 import javafx.scene.Parent;
  40 import javafx.scene.transform.Transform;
  41 import javafx.stage.Popup;
  42 import javafx.stage.Window;
  43 import javafx.stage.WindowEvent;
  44 
  45 /**
  46  *
  47  * p
  48  */
  49 public abstract class AbstractPopupController {
  50 
  51     private Parent root;
  52     private Popup popup;
  53     private Node anchor;
  54     private Window anchorWindow;
  55 
  56     /**
  57      * Returns the root FX object of this popup.
  58      * When called the first time, this method invokes {@link #makeRoot()}
  59      * to build the FX components of the panel.
  60      *
  61      * @return the root object of the panel (never null)
  62      */
  63     public Parent getRoot() {
  64         if (root == null) {
  65             makeRoot();
  66             assert root != null;
  67         }
  68 
  69         return root;
  70     }
  71 
  72     public Popup getPopup() {
  73         assert Platform.isFxApplicationThread();
  74 
  75         if (popup == null) {
  76             popup = new Popup();
  77             popup.getContent().add(getRoot());
  78             popup.setOnHidden(onHiddenHandler);
  79             controllerDidCreatePopup();
  80         }
  81 
  82         return popup;
  83     }
  84 
  85 
  86     public void openWindow(Node anchor) {
  87         assert Platform.isFxApplicationThread();
  88         assert anchor != null;
  89         assert anchor.getScene() != null;
  90         assert anchor.getScene().getWindow() != null;
  91 
  92         this.anchor = anchor;
  93         this.anchorWindow = anchor.getScene().getWindow();
  94 
  95         this.anchor.layoutBoundsProperty().addListener(layoutBoundsListener);
  96         this.anchor.localToSceneTransformProperty().addListener(localToSceneTransformListener);
  97         this.anchorWindow.xProperty().addListener(xyListener);
  98 
  99         getPopup().show(this.anchor.getScene().getWindow());
 100         anchorBoundsDidChange();
 101         updatePopupLocation();
 102     }
 103 
 104     public void closeWindow() {
 105         assert Platform.isFxApplicationThread();
 106         getPopup().hide();
 107         // Note : Popup.hide() will invoke onHiddenHandler() which
 108         // will remove listeners set by openWindow.
 109     }
 110 
 111     public boolean isWindowOpened() {
 112         return (popup == null) ? false : popup.isShowing();
 113     }
 114 
 115     public Node getAnchor() {
 116         return anchor;
 117     }
 118 
 119 
 120     /*
 121      * To be implemented by subclasses
 122      */
 123 
 124     /**
 125      * Creates the FX object composing the panel.
 126      * This routine is called by {@link AbstractPopupController#getRoot}.
 127      * It *must* invoke {@link AbstractPanelController#setPanelRoot}.
 128      *
 129      */
 130     protected abstract void makeRoot();
 131 
 132     protected abstract void onHidden(WindowEvent event);
 133 
 134     protected abstract void anchorBoundsDidChange();
 135 
 136     protected abstract void anchorTransformDidChange();
 137 
 138     protected abstract void anchorXYDidChange();
 139 
 140     protected void controllerDidCreatePopup() {
 141         assert getRoot() != null;
 142         assert getRoot().getScene() != null;
 143     }
 144 
 145     protected abstract void updatePopupLocation();
 146 
 147 
 148     /*
 149      * For subclasses
 150      */
 151 
 152     /**
 153      * Set the root of this popup controller.
 154      * This routine must be invoked by subclass's makeRoot() routine.
 155      *
 156      * @param panelRoot the root panel (non null).
 157      */
 158     protected  final void setRoot(Parent panelRoot) {
 159         assert panelRoot != null;
 160         this.root = panelRoot;
 161     }
 162 
 163 
 164     /*
 165      * Private
 166      */
 167 
 168     private final ChangeListener<Bounds> layoutBoundsListener
 169     = (ov, t, t1) -> anchorBoundsDidChange();
 170 
 171 
 172     private final ChangeListener<Transform> localToSceneTransformListener
 173     = (ov, t, t1) -> anchorTransformDidChange();
 174 
 175 
 176     private final ChangeListener<Number> xyListener
 177     = (ov, t, t1) -> anchorXYDidChange();
 178 
 179     private final EventHandler<WindowEvent> onHiddenHandler = e -> {
 180         assert anchor != null;
 181 
 182         onHidden(e);
 183 
 184         anchor.layoutBoundsProperty().removeListener(layoutBoundsListener);
 185         anchor.localToSceneTransformProperty().removeListener(localToSceneTransformListener);
 186         anchorWindow.xProperty().removeListener(xyListener);
 187 
 188         anchor = null;
 189         anchorWindow = null;
 190     };
 191 }