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 }