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 com.oracle.javafx.scenebuilder.kit.editor.EditorController; 35 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMDocument; 36 37 import java.util.List; 38 import java.util.logging.Level; 39 import java.util.logging.Logger; 40 41 import javafx.beans.value.ChangeListener; 42 import javafx.scene.Parent; 43 44 /** 45 * AbstractPanelController is the abstract base class for all the panel 46 * controllers of Scene Builder Kit. 47 * <p> 48 * At instantiation time, each panel controller is passed a reference to its 49 * editor controller which is hold in <code>editorController</code>. 50 * <p> 51 * Subclasses must provide three methods: 52 * <ul> 53 * <li><code>makePanel</code> must create the FX components 54 * which compose the panel 55 * <li><code>fxomDocumentDidChange</code> must keep the panel up to date 56 * after the editor controller has changed the base document 57 * <li><code>editorSelectionDidChange</code> must keep the panel up to date 58 * after the editor controller has changed the selected objects. 59 * </ul> 60 * 61 * 62 */ 63 public abstract class AbstractPanelController { 64 65 private static final Logger LOG = Logger.getLogger(AbstractPanelController.class.getName()); 66 67 private final EditorController editorController; 68 private Parent panelRoot; 69 70 /** 71 * Base constructor for invocation by the subclasses. 72 * Subclass implementations should make sure that this constructor can be 73 * invoked outside of the JavaFX thread. 74 * 75 * @param c the editor controller (should not be null). 76 */ 77 protected AbstractPanelController(EditorController c) { 78 assert c != null; 79 this.editorController = c; 80 startListeningToEditorSelection(); 81 startListeningToJobManagerRevision(); 82 editorController.fxomDocumentProperty().addListener((ChangeListener<FXOMDocument>) (ov, od, nd) -> { 83 assert editorController.getFxomDocument() == nd; 84 if (od != null) { 85 od.sceneGraphRevisionProperty().removeListener(fxomDocumentRevisionListener); 86 od.cssRevisionProperty().removeListener(cssRevisionListener); 87 } 88 try { 89 fxomDocumentDidChange(od); 90 } catch(RuntimeException x) { 91 LOG.log(Level.SEVERE, "Bug", x); //NOI18N 92 } 93 if (nd != null) { 94 nd.sceneGraphRevisionProperty().addListener(fxomDocumentRevisionListener); 95 nd.cssRevisionProperty().addListener(cssRevisionListener); 96 } 97 }); 98 if (editorController.getFxomDocument() != null) { 99 editorController.getFxomDocument().sceneGraphRevisionProperty().addListener(fxomDocumentRevisionListener); 100 editorController.getFxomDocument().cssRevisionProperty().addListener(cssRevisionListener); 101 } 102 editorController.toolStylesheetProperty().addListener((ChangeListener<String>) (ov, od, nd) -> toolStylesheetDidChange(od)); 103 } 104 105 /** 106 * Returns the editor controller associated to this panel controller. 107 * 108 * @return the editor controller (never null). 109 */ 110 public EditorController getEditorController() { 111 return editorController; 112 } 113 114 /** 115 * Returns the root FX object of this panel. 116 * When called the first time, this method invokes {@link #makePanel()} 117 * to build the FX components of the panel. 118 * 119 * @return the root object of the panel (never null) 120 */ 121 public Parent getPanelRoot() { 122 if (panelRoot == null) { 123 makePanel(); 124 assert panelRoot != null; 125 126 // Installs the stylesheet from the editor controller 127 final List<String> stylesheets = panelRoot.getStylesheets(); 128 if (stylesheets.contains(EditorController.getBuiltinToolStylesheet())) { 129 toolStylesheetDidChange(EditorController.getBuiltinToolStylesheet()); 130 } else { 131 toolStylesheetDidChange(null); 132 } 133 } 134 135 return panelRoot; 136 } 137 138 /* 139 * To be implemented by subclasses 140 */ 141 142 /** 143 * Creates the FX object composing the panel. 144 * This routine is called by {@link AbstractPanelController#getPanelRoot}. 145 * It *must* invoke {@link AbstractPanelController#setPanelRoot}. 146 */ 147 protected abstract void makePanel(); 148 149 /** 150 * Updates the panel after the editor controller has change 151 * the base document. Subclass can use {@link EditorController#getFxomDocument() } 152 * to retrieve the newly set document (possibly null). 153 * 154 * @param oldDocument the previous document (possibly null). 155 */ 156 protected abstract void fxomDocumentDidChange(FXOMDocument oldDocument); 157 158 /** 159 * Updates the panel after the revision of the scene graph has changed. 160 * Revision is incremented each time the fxom document rebuilds the 161 * scene graph. 162 */ 163 protected abstract void sceneGraphRevisionDidChange(); 164 165 /** 166 * Updates the panel after the css revision has changed. 167 * Revision is incremented each time the fxom document forces FX to 168 * reload its stylesheets. 169 */ 170 protected abstract void cssRevisionDidChange(); 171 172 /** 173 * Updates the panel after the revision of job manager has changed. 174 * Revision is incremented each time a job is executed, undone or redone. 175 */ 176 protected abstract void jobManagerRevisionDidChange(); 177 178 /** 179 * Updates the panel after the editor controller has changed the selected 180 * objects. Subclass can use {@link EditorController#getSelection()} to 181 * retrieve the currently selected objects. 182 */ 183 protected abstract void editorSelectionDidChange(); 184 185 186 /* 187 * For subclasses 188 */ 189 190 /** 191 * Set the root of this panel controller. 192 * This routine must be invoked by subclass's makePanel() routine. 193 * 194 * @param panelRoot the root panel (non null). 195 */ 196 protected final void setPanelRoot(Parent panelRoot) { 197 assert panelRoot != null; 198 this.panelRoot = panelRoot; 199 } 200 201 private final ChangeListener<Number> fxomDocumentRevisionListener 202 = (observable, oldValue, newValue) -> { 203 try { 204 sceneGraphRevisionDidChange(); 205 } catch(RuntimeException x) { 206 LOG.log(Level.SEVERE, "Bug", x); //NOI18N 207 } 208 }; 209 210 private final ChangeListener<Number> cssRevisionListener 211 = (observable, oldValue, newValue) -> { 212 try { 213 cssRevisionDidChange(); 214 } catch(RuntimeException x) { 215 LOG.log(Level.SEVERE, "Bug", x); //NOI18N 216 } 217 }; 218 219 private final ChangeListener<Number> jobManagerRevisionListener 220 = (observable, oldValue, newValue) -> { 221 try { 222 jobManagerRevisionDidChange(); 223 } catch(RuntimeException x) { 224 LOG.log(Level.SEVERE, "Bug", x); //NOI18N 225 } 226 }; 227 228 private final ChangeListener<Number> editorSelectionListener = 229 (observable, oldValue, newValue) -> { 230 try { 231 editorSelectionDidChange(); 232 } catch(RuntimeException x) { 233 LOG.log(Level.SEVERE, "Bug", x); //NOI18N 234 } 235 }; 236 237 /** 238 * Setup a listener which invokes {@link #editorSelectionDidChange} each 239 * time the editor controller changes the selected objects. 240 * This routine is automatically called when the panel controller is 241 * instantiated. Subclasses may invoke it after temporarily disabling 242 * selection listening with {@link AbstractPanelController#stopListeningToEditorSelection}. 243 */ 244 protected final void startListeningToEditorSelection() { 245 editorController.getSelection().revisionProperty().addListener(editorSelectionListener); 246 } 247 248 /** 249 * Removes the listener which invokes {@link #editorSelectionDidChange} each 250 * time the editor controller changes the selected objects. 251 * Subclasses may invoke this routine to temporarily stop listening to 252 * the selection changes from the editor controller. Use 253 * {@link AbstractPanelController#startListeningToEditorSelection} to 254 * re-enable selection listening. 255 */ 256 protected final void stopListeningToEditorSelection() { 257 editorController.getSelection().revisionProperty().removeListener(editorSelectionListener); 258 } 259 260 261 /** 262 * Setup a listener which invokes {@link #jobManagerRevisionDidChange} each 263 * time the job manager has executed, undone or redone a job. 264 * This routine is automatically called when the panel controller is 265 * instantiated. Subclasses may invoke it after temporarily disabling 266 * job manager listening with {@link AbstractPanelController#stopListeningToJobManagerRevision}. 267 */ 268 protected final void startListeningToJobManagerRevision() { 269 editorController.getJobManager().revisionProperty().addListener(jobManagerRevisionListener); 270 } 271 272 273 /** 274 * Removes the listener which invokes {@link #jobManagerRevisionDidChange} each 275 * time the job manager has executed, undone or redone a job. 276 * Subclasses may invoke this routine to temporarily stop listening to 277 * the job manager from the editor controller. Use 278 * {@link AbstractPanelController#startListeningToJobManagerRevision} to 279 * re-enable job manager listening. 280 */ 281 protected final void stopListeningToJobManagerRevision() { 282 editorController.getJobManager().revisionProperty().removeListener(jobManagerRevisionListener); 283 } 284 285 286 /** 287 * Replaces oldStylesheet by the tool style sheet assigned to the editor 288 * controller. This methods {@link EditorController#getToolStylesheet}. 289 * 290 * @param oldStylesheet null or the style sheet to be replaced 291 */ 292 protected void toolStylesheetDidChange(String oldStylesheet) { 293 /* 294 * Tool style sheet has changed in editor controller. 295 * If the panel has been loaded, then we replace the old sheet 296 * by the new one in the stylesheets property of its root object. 297 */ 298 if (panelRoot != null) { 299 final List<String> stylesheets = panelRoot.getStylesheets(); 300 if (oldStylesheet != null) { 301 stylesheets.remove(oldStylesheet); 302 } 303 stylesheets.add(editorController.getToolStylesheet()); 304 } 305 } 306 307 308 }