       1 /*
       2  * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
       3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       4  *
       5  * This code is free software; you can redistribute it and/or modify it
       6  * under the terms of the GNU General Public License version 2 only, as
       7  * published by the Free Software Foundation.  Oracle designates this
       8  * particular file as subject to the "Classpath" exception as provided
       9  * by Oracle in the LICENSE file that accompanied this code.
      10  *
      11  * This code is distributed in the hope that it will be useful, but WITHOUT
      12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
      13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
      14  * version 2 for more details (a copy is included in the LICENSE file that
      15  * accompanied this code).
      16  *
      17  * You should have received a copy of the GNU General Public License version
      18  * 2 along with this work; if not, write to the Free Software Foundation,
      19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
      20  *
      21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
      22  * or visit www.oracle.com if you need additional information or have any
      23  * questions.
      24  */
      25
      26 package com.sun.javafx.css;
      27
      28 import com.sun.javafx.logging.PlatformLogger;
      29 import java.util.HashMap;
      30 import java.util.List;
      31 import java.util.Map;
      32 import javafx.scene.Scene;
      33
      34 import javafx.beans.value.ChangeListener;
      35 import javafx.beans.value.ObservableValue;
      36 import javafx.collections.*;
      37 import javafx.collections.ListChangeListener.Change;
      38 import javafx.scene.Node;
      39 import javafx.stage.PopupWindow;
      40 import javafx.stage.Window;
      41
      42 /**
      43  * Contains the stylesheet state for a single scene.
      44  */
      45 final class SceneStyleManager extends StyleManager<Scene> {
      46
      47     /**
      48      *
      49      * @param scene
      50      */
      51     public SceneStyleManager(Scene scene) {
      52
      53         super(scene);
      54
      55         this.popupOwnerScene = scene;
      56
      57         this.key = new StyleManager.Key();
      58
      59         // We have special handling for the case of a node which is in a popup
      60         // scene. The problem is that the root scene used for the purpose of style
      61         // sheet support isn't the scene of the popup (which is a hidden API
      62         // member), but rather the main stage's scene. So we need the scene of the
      63         // root stage.
      64         Window window = scene.getWindow();
      65         while (window instanceof PopupWindow) {
      66             window = ((PopupWindow)window).getOwnerWindow();
      67         }
      68
      69         // A popup owner's window might not be set yet.
      70         if (window == null) scene.windowProperty().addListener(windowListener);
      71     }
      72
      73     private final ChangeListener<Window> windowListener = new ChangeListener<Window>() {
      74
      75         @Override
      76         public void changed(ObservableValue<? extends Window> observable, Window oldValue, Window newValue) {
      77
      78             if (newValue != null) {
      79
      80                 observable.removeListener(this);
      81
      82                 if (newValue instanceof PopupWindow) {
      83
      84                     //
      85                     // Find the owner window of this popup
      86                     //
      87                     Window window = newValue;
      88                     PopupWindow popup = null;
      89                     while (window instanceof PopupWindow) {
      90                         popup = (PopupWindow)window;
      91                         window = popup.getOwnerWindow();
      92                     }
      93
      94                     Scene popupOwnerScene = null;
      95                     if (window != null) {
      96                         popupOwnerScene = window.getScene();
      97                     }
      98
      99                     if (popupOwnerScene != null) {
     100                         setPopupOwnerScene(popupOwnerScene);
     101                     } else {
     102                         //
     103                         // Still don't have the root Scene. Keep listening.
     104                         //
     105                         popup.ownerWindowProperty().addListener(this);
     106                     }
     107
     108                 } else {
     109
     110                     // if the newWindow isn't a popup window, then
     111                     // we've found the owner window
     112                     setPopupOwnerScene(newValue.getScene());
     113                 }
     114             }
     115         }
     116     };
     117
     118     private void setPopupOwnerScene(Scene popupOwnerScene) {
     119
     120         assert(popupOwnerScene != null);
     121
     122         if (popupOwnerScene != this.owner) {
     123
     124             // If the root Scene's stylesheets change, then this popup's styles
     125             // need to be updated.
     126             popupOwnerScene.getStylesheets().addListener(new ListChangeListener<String>() {
     127
     128                 @Override
     129                 public void onChanged(Change<? extends String> c) {
     130                     updateStylesheets();
     131                 }
     132             });
     133             this.popupOwnerScene = popupOwnerScene;
     134             owner.getRoot().impl_reapplyCSS();
     135
     136         }
     137
     138     }
     139
     140     private final StyleManager.Key key;
     141
     142     /*
     143      * In the case of a popup, this is the Scene of the ownerWindow. If
     144      * the Scene's window is not a PopupWindow, then the rootScene == scene.
     145      */
     146     private Scene popupOwnerScene; // TODO: potential leak?
     147     Scene getPopupOwnerScene() { return popupOwnerScene; } // for testing
     148
     149     /**
     150      * A map from String => Stylesheet for a Scene. If a stylesheet for the
     151      * given URL has already been loaded then we'll simply reuse the stylesheet
     152      * rather than loading a duplicate.
     153      */
     154     private static final Map<String,StylesheetContainer<Scene>> sceneStylesheetMap
     155         = new HashMap<String,StylesheetContainer<Scene>>();
     156
     157
     158     ////////////////////////////////////////////////////////////////////////////
     159     //
     160     // Author stylesheet handling
     161     //
     162     ////////////////////////////////////////////////////////////////////////////
     163
     164     /**
     165      * Creates stylesheets and associates them with the given scene.
     166      *
     167      * Called from Scene.stylesheets on replace. Attempts to reuse existing
     168      * stylesheets for duplicate urls (ie: if two stages are loaded with the same
     169      * stylesheet urls, then the same stylesheet instance will be reused).
     170      *
     171      * All applied styles and caches are deleted, and complete clean application
     172      * of styles is started with new stylesheets.
     173      *
     174      * @param scene     The scene to update stylesheets for
     175      */
     176     public void updateStylesheets() {
     177
     178         // The list of referenced stylesheets is about to be recalculated,
     179         // so clear the current list. The style maps are no longer valid, so
     180         // annihilate the cache
     181         referencedStylesheets.clear();
     182         clearCache();
     183
     184         if (defaultUserAgentStylesheet != null) {
     185             referencedStylesheets.add(defaultUserAgentStylesheet);
     186         }
     187
     188         if (userAgentStylesheetMap.isEmpty() == false) {
     189             // TODO: This isn't right. They should be in the same order that
     190             // they were first added.
     191             referencedStylesheets.addAll(userAgentStylesheetMap.values());
     192         }
     193
     194         // RT-20643
     195         CssError.setCurrentScene(owner);
     196
     197         // create the stylesheets, one per URL supplied
     198         List<String> stylesheetURLs = popupOwnerScene == owner
     199                 ? owner.getStylesheets()
     200                 : popupOwnerScene.getStylesheets();
     201
     202         for (int i = 0; i < stylesheetURLs.size(); i++) {
     203
     204             String url = stylesheetURLs.get(i);
     205             url = url != null ? url.trim() : null;
     206             if (url == null || url.isEmpty()) continue;
     207
     208             // Has someone already parsed this for us?
     209             StylesheetContainer container = sceneStylesheetMap.get(url);
     210             if (container != null) {
     211
     212                 // let it be known that this Scene uses this stylesheet
     213                 container.users.add(owner);
     214
     215                 final Stylesheet stylesheet = container.stylesheet;
     216                 if (stylesheet != null) {
     217                     referencedStylesheets.add(stylesheet);
     218                 }
     219
     220             } else {
     221
     222                 Stylesheet stylesheet = null;
     223                 try {
     224
     225                     stylesheet = loadStylesheet(url);
     226
     227                 } catch (Exception e) {
     228                     // If an exception occurred while loading one of the stylesheets
     229                     // then we will simply print warning into system.err and skip the
     230                     // stylesheet, allowing other stylesheets to attempt to load
     231                     if (LOGGER.isLoggable(PlatformLogger.WARNING)) {
     232                         LOGGER.warning("Cannot add stylesheet. %s\n", e.getLocalizedMessage());
     233                     }
     234                     // no telling what condition stylesheet is in. make it null
     235                     stylesheet = null;
     236
     237                 } finally {
     238
     239                     // stylesheet is added to the container even if it is null
     240                     // in order to prevent further attempts at parsing it.
     241                     container = new StylesheetContainer<Scene>(url, stylesheet);
     242                     container.users.add(owner);
     243                     if (stylesheet != null) {
     244                         referencedStylesheets.add(stylesheet);
     245                     }
     246
     247                 }
     248             }
     249         }
     250
     251         // RT-20643
     252         CssError.setCurrentScene(null);
     253
     254     }
     255
     256     @Override
     257     protected Key getKey(Node node) {
     258
     259         // Populate our helper key with the class name, id, and style class
     260         // of the node and lookup the associated Cache in the cacheMap
     261         key.className = node.getClass().getName();
     262         key.id = node.getId();
     263         key.styleClass = node.getStyleClass();
     264
     265         return key;
     266
     267     }
     268
     269 }