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 }