1 /*
   2  * Copyright (c) 2012, 2013, 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.FXUnit;
  29 import com.sun.javafx.css.converters.FontConverter;
  30 import com.sun.javafx.css.converters.SizeConverter;
  31 import com.sun.javafx.tk.Toolkit;
  32 import java.util.ArrayList;
  33 import java.util.Collections;
  34 import java.util.HashMap;
  35 import java.util.List;
  36 import java.util.Map;
  37 import javafx.collections.FXCollections;
  38 import javafx.collections.MapChangeListener;
  39 import javafx.css.CssMetaData;
  40 import javafx.css.ParsedValue;
  41 import javafx.css.StyleOrigin;
  42 import javafx.css.StyleableProperty;
  43 import javafx.scene.Group;
  44 import javafx.scene.Node;
  45 import javafx.scene.Scene;
  46 import javafx.scene.paint.Color;
  47 import javafx.scene.shape.Rectangle;
  48 import javafx.scene.text.Font;
  49 import javafx.scene.text.Text;
  50 import javafx.stage.Stage;
  51 import static org.junit.Assert.*;
  52 
  53 import org.junit.Ignore;
  54 import org.junit.Test;
  55 
  56 public class Node_cssStyleMap_Test {
  57 
  58     @org.junit.Rule
  59     public FXUnit fx = new FXUnit();
  60 
  61     public Node_cssStyleMap_Test() {
  62     }
  63 
  64     boolean disabled = false;
  65     int nadds = 0;
  66     int nremoves = 0;
  67 
  68     static List<CascadingStyle> createStyleList(List<Declaration> decls) {
  69         
  70         final List<CascadingStyle> styles = new ArrayList<CascadingStyle>();
  71         
  72         for (Declaration decl : decls) {
  73             styles.add(
  74                 new CascadingStyle(
  75                     new Style(decl.rule.getUnobservedSelectorList().get(0), decl),
  76                     new PseudoClassState(),
  77                     0, 
  78                     0
  79                 )
  80             );
  81         }
  82         
  83         return styles;
  84     }
  85     
  86     static Map<String, List<CascadingStyle>> createStyleMap(List<CascadingStyle> styles) {
  87         
  88         final Map<String, List<CascadingStyle>> smap = 
  89             new HashMap<String, List<CascadingStyle>>();
  90         
  91         final int max = styles != null ? styles.size() : 0;
  92         for (int i=0; i<max; i++) {
  93             final CascadingStyle style = styles.get(i);
  94             final String property = style.getProperty();
  95             // This is carefully written to use the minimal amount of hashing.
  96             List<CascadingStyle> list = smap.get(property);
  97             if (list == null) {
  98                 list = new ArrayList<CascadingStyle>(5);
  99                 smap.put(property, list);
 100             }
 101             list.add(style);
 102         }
 103         return smap;
 104     }
 105     
 106     @Test @Ignore ("Pending RT-34463")
 107     public void testStyleMapTracksChanges() {
 108                 
 109         final List<Declaration> declsNoState = new ArrayList<Declaration>();
 110         Collections.addAll(declsNoState, 
 111             new Declaration("-fx-fill", new ParsedValueImpl<Color,Color>(Color.RED, null), false),
 112             new Declaration("-fx-stroke", new ParsedValueImpl<Color,Color>(Color.YELLOW, null), false),
 113             new Declaration("-fx-stroke-width", new ParsedValueImpl<ParsedValue<?,Size>,Number>(
 114                 new ParsedValueImpl<Size,Size>(new Size(3d, SizeUnits.PX), null), 
 115                 SizeConverter.getInstance()), false)
 116         );
 117         
 118         
 119         final List<Selector> selsNoState = new ArrayList<Selector>();
 120         Collections.addAll(selsNoState, 
 121             Selector.createSelector(".rect")
 122         );
 123         
 124         Rule rule = new Rule(selsNoState, declsNoState);        
 125         
 126         Stylesheet stylesheet = new Stylesheet("testStyleMapTracksChanges");
 127         stylesheet.setOrigin(StyleOrigin.USER_AGENT);
 128         stylesheet.getRules().add(rule);
 129         
 130         final List<Declaration> declsDisabledState = new ArrayList<Declaration>();
 131         Collections.addAll(declsDisabledState, 
 132             new Declaration("-fx-fill", new ParsedValueImpl<Color,Color>(Color.GRAY, null), false),
 133             new Declaration("-fx-stroke", new ParsedValueImpl<Color,Color>(Color.DARKGRAY, null), false)
 134         );
 135         
 136         final List<Selector> selsDisabledState = new ArrayList<Selector>();
 137         Collections.addAll(selsDisabledState, 
 138             Selector.createSelector(".rect:disabled")
 139         );
 140         
 141         rule = new Rule(selsDisabledState, declsDisabledState);        
 142         stylesheet.getRules().add(rule);
 143         
 144         final List<CascadingStyle> stylesNoState = createStyleList(declsNoState);
 145         final List<CascadingStyle> stylesDisabledState = createStyleList(declsDisabledState);
 146         
 147         // add to this list on wasAdded, check bean on wasRemoved.
 148         final List<StyleableProperty<?>> beans = new ArrayList<StyleableProperty<?>>();
 149         Rectangle rect = new Rectangle(50,50);
 150         rect.getStyleClass().add("rect");
 151         rect.impl_setStyleMap(FXCollections.observableMap(new HashMap<StyleableProperty<?>, List<Style>>()));
 152         rect.impl_getStyleMap().addListener(new MapChangeListener<StyleableProperty, List<Style>>() {
 153 
 154             public void onChanged(MapChangeListener.Change<? extends StyleableProperty, ? extends List<Style>> change) {
 155 
 156                 if (change.wasAdded()) {
 157                     
 158                     List<Style> styles = change.getValueAdded();
 159                     for (Style style : styles) {
 160 
 161                         // stroke width comes from ".rect" even for disabled state.
 162                         if (disabled == false || "-fx-stroke-width".equals(style.getDeclaration().getProperty())) {
 163                             assertTrue(style.getDeclaration().toString(),declsNoState.contains(style.getDeclaration()));
 164                             assertTrue(style.getSelector().toString(),selsNoState.contains(style.getSelector()));
 165                         } else {
 166                             assertTrue(style.getDeclaration().toString(),declsDisabledState.contains(style.getDeclaration()));
 167                             assertTrue(style.getSelector().toString(),selsDisabledState.contains(style.getSelector()));                            
 168                         }
 169                         Object value = style.getDeclaration().parsedValue.convert(null);
 170                         StyleableProperty styleableProperty = change.getKey();
 171                         beans.add(styleableProperty);
 172                         assertEquals(styleableProperty.getValue(), value);
 173                         nadds += 1;                        
 174                     }
 175                     
 176                 } if (change.wasRemoved()) {
 177                     StyleableProperty styleableProperty = change.getKey();
 178                     assert(beans.contains(styleableProperty));
 179                     nremoves += 1;
 180                 }
 181             }
 182         });
 183 
 184         Group root = new Group();
 185         root.getChildren().add(rect);
 186         StyleManager.getInstance().setDefaultUserAgentStylesheet(stylesheet);        
 187         Scene scene = new Scene(root);
 188         Stage stage = new Stage();
 189         stage.setScene(scene);
 190         stage.show();
 191 
 192         // The three no state styles should be applied
 193         assertEquals(3, nadds);
 194         assertEquals(0, nremoves);
 195 
 196         rect.setDisable(true);
 197         disabled = true;
 198         nadds = 0;
 199         nremoves = 0;
 200         
 201         Toolkit.getToolkit().firePulse();
 202         
 203         // The three no state styles should be removed and the 
 204         // two disabled state styles plus the stroke width style 
 205         // should be applied. 
 206         assertEquals(3, nadds);
 207         assertEquals(3, nremoves);
 208         
 209     }
 210     
 211     @Test @Ignore ("Pending RT-34463")
 212     public void testRT_21212() {
 213 
 214         final List<Declaration> rootDecls = new ArrayList<Declaration>();
 215         Collections.addAll(rootDecls, 
 216             new Declaration("-fx-font-size", new ParsedValueImpl<ParsedValue<?,Size>,Number>(
 217                 new ParsedValueImpl<Size,Size>(new Size(12, SizeUnits.PX), null), 
 218                 SizeConverter.getInstance()), false)
 219         );
 220         
 221         final List<Selector> rootSels = new ArrayList<Selector>();
 222         Collections.addAll(rootSels, 
 223             Selector.createSelector(".root")
 224         );
 225         
 226         Rule rootRule = new Rule(rootSels, rootDecls);        
 227         
 228         Stylesheet stylesheet = new Stylesheet("testRT_21212");
 229         stylesheet.setOrigin(StyleOrigin.USER_AGENT);
 230         stylesheet.getRules().add(rootRule);
 231 
 232         final List<CascadingStyle> rootStyles = createStyleList(rootDecls);
 233         final Map<String,List<CascadingStyle>> rootStyleMap = createStyleMap(rootStyles);
 234         final Map<StyleCache.Key, StyleCache> styleCache = 
 235             new HashMap<StyleCache.Key, StyleCache>();
 236         
 237         Group group = new Group();
 238         group.getStyleClass().add("root");
 239         
 240         
 241         final ParsedValue[] fontValues = new ParsedValue[] {
 242             new ParsedValueImpl<String,String>("system", null),
 243             new ParsedValueImpl<ParsedValue<?,Size>,Number>(
 244                 new ParsedValueImpl<Size,Size>(new Size(1.5, SizeUnits.EM), null),
 245                 SizeConverter.getInstance()
 246             ), 
 247             null,
 248             null
 249         };
 250         final List<Declaration> textDecls = new ArrayList<Declaration>();
 251         Collections.addAll(textDecls, 
 252             new Declaration("-fx-font", new ParsedValueImpl<ParsedValue[], Font>(
 253                 fontValues, FontConverter.getInstance()), false)
 254         );
 255         
 256         final List<Selector> textSels = new ArrayList<Selector>();
 257         Collections.addAll(textSels, 
 258             Selector.createSelector(".text")
 259         );
 260         
 261         Rule textRule = new Rule(textSels, textDecls);        
 262         stylesheet.getRules().add(textRule);
 263                 
 264         final List<CascadingStyle> styles = createStyleList(textDecls);
 265         final Map<String,List<CascadingStyle>> styleMap = createStyleMap(styles);
 266         final Map<String,List<CascadingStyle>> emptyMap = createStyleMap(null);
 267 
 268         Text text = new Text("HelloWorld");
 269         group.getChildren().add(text);
 270 
 271         final List<Declaration> expecteds = new ArrayList<Declaration>();
 272         expecteds.addAll(rootDecls);
 273         expecteds.addAll(textDecls);
 274         text.getStyleClass().add("text");
 275         text.impl_setStyleMap(FXCollections.observableMap(new HashMap<StyleableProperty<?>, List<Style>>()));
 276         text.impl_getStyleMap().addListener(new MapChangeListener<StyleableProperty, List<Style>>() {
 277 
 278             // a little different than the other tests since we should end up 
 279             // with font and font-size in the map and nothing else. After all 
 280             // the changes have been handled, the expecteds list should be empty.
 281             public void onChanged(MapChangeListener.Change<? extends StyleableProperty, ? extends List<Style>> change) {
 282                 if (change.wasAdded()) {
 283                     List<Style> styles = change.getValueAdded();
 284                     for (Style style : styles) {
 285                         assertTrue(expecteds.contains(style.getDeclaration()));
 286                         expecteds.remove(style.getDeclaration());
 287                     }
 288                 }
 289             }
 290         });
 291              
 292         StyleManager.getInstance().setDefaultUserAgentStylesheet(stylesheet);        
 293         Scene scene = new Scene(group);
 294         Stage stage = new Stage();
 295         stage.setScene(scene);
 296         stage.show();
 297 
 298         assertEquals(18, text.getFont().getSize(),0);
 299         assertTrue(Integer.toString(expecteds.size()), expecteds.isEmpty());
 300 
 301     }
 302 
 303     boolean containsProperty(CssMetaData key, Map<String,List<CascadingStyle>> map) {
 304 
 305         if (map.containsKey(key)) return true;
 306         List<CssMetaData> subProperties = key.getSubProperties();
 307         if (subProperties != null && !subProperties.isEmpty()) {
 308             for (CssMetaData subKey: subProperties) {
 309                 if (map.containsKey(subKey)) return true;
 310             }
 311         }
 312         return false;
 313     }
 314 
 315     @Test
 316     public void testRT_34799() {
 317 
 318         Stylesheet stylesheet = new Stylesheet("testRT_34799");
 319         stylesheet.setOrigin(StyleOrigin.USER_AGENT);
 320 
 321         final List<Declaration> txtDecls = new ArrayList<Declaration>();
 322         Collections.addAll(txtDecls,
 323                 new Declaration("-fx-fill", new ParsedValueImpl<Color,Color>(Color.RED, null), false)
 324         );
 325 
 326         final List<Selector> textSels = new ArrayList<Selector>();
 327         Collections.addAll(textSels,
 328                 Selector.createSelector(".rt-34799")
 329         );
 330 
 331         Rule txtRules = new Rule(textSels, txtDecls);
 332         stylesheet.getRules().add(txtRules);
 333 
 334         final List<Style> expectedStyles = new ArrayList<>();
 335         for (Rule rule : stylesheet.getRules()) {
 336             for (Selector selector : rule.getSelectors()) {
 337                 for (Declaration declaration : rule.getUnobservedDeclarationList()) {
 338                     expectedStyles.add(
 339                             new Style(selector, declaration)
 340                     );
 341                 }
 342             }
 343         }
 344 
 345         Text text = new Text("HelloWorld");
 346         text.getStyleClass().add("rt-34799");
 347 
 348         Group group = new Group();
 349         group.getStyleClass().add("root");
 350 
 351         group.getChildren().add(text);
 352 
 353         StyleManager.getInstance().setDefaultUserAgentStylesheet(stylesheet);
 354         Scene scene = new Scene(group);
 355 
 356         group.applyCss(); // TODO: force StyleHelper to be created, remove pending RT-34812
 357 
 358         int nExpected = expectedStyles.size();
 359         assert(nExpected > 0);
 360 
 361         for(CssMetaData cssMetaData : text.getCssMetaData()) {
 362             List<Style> styles = Node.impl_getMatchingStyles(cssMetaData, text);
 363             if (styles != null && !styles.isEmpty()) {
 364                 assertTrue(expectedStyles.containsAll(styles));
 365                 assertTrue(styles.containsAll(expectedStyles));
 366                 nExpected -= 1;
 367             }
 368         }
 369 
 370         assertEquals(nExpected, 0);
 371 
 372     }
 373 
 374 }