/*******************************************************************************
* Copyright (c) 2011 BestSolution.at and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Tom Schindl<tom.schindl@bestsolution.at> - initial API and implementation
*******************************************************************************/
package at.bestsolution.efxclipse.tooling.css.jfx;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import org.eclipse.emf.common.util.URI;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.ColorDialog;
import org.eclipse.ui.PlatformUI;
import at.bestsolution.efxclipse.tooling.css.CssDialectExtension;
import at.bestsolution.efxclipse.tooling.css.cssDsl.CssDslPackage;
import at.bestsolution.efxclipse.tooling.css.cssDsl.css_declaration;
import at.bestsolution.efxclipse.tooling.css.cssDsl.css_generic_declaration;
import at.bestsolution.efxclipse.tooling.css.cssDsl.expr;
import at.bestsolution.efxclipse.tooling.css.cssDsl.term;
import at.bestsolution.efxclipse.tooling.css.cssDsl.termGroup;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.Group;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.Node;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.chart.AreaChart;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.chart.BarChart;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.chart.BarChart3D;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.chart.BubbleChart;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.chart.Chart;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.chart.LineChart;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.chart.PieChart;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.chart.PieChart3D;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.chart.ScatterChart;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.chart.XYChart;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.chart.part.Axis;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.chart.part.CategoryAxis;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.chart.part.Legend;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.chart.part.NumberAxis;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.chart.part.PlotSymbol;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.chart.part.ValueAxis;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.control.Label;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.control.Labeled;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.control.MenuBar;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.control.PasswordBox;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.control.Region;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.control.ScrollBar;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.control.ScrollView;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.control.Separator;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.control.Slider;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.control.TextBox;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.control.TextInputControl;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.image.ImageView;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.layout.ClipView;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.layout.Container;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.layout.Flow;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.layout.HBox;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.layout.Stack;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.layout.Tile;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.layout.VBox;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.shape.Rectangle;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.shape.Shape;
import at.bestsolution.efxclipse.tooling.css.jfx.scene.text.Text;
import at.bestsolution.efxclipse.tooling.css.jfx.validators.GradientValidator;
import at.bestsolution.efxclipse.tooling.css.jfx.validators.LinearGradientValidator;
import at.bestsolution.efxclipse.tooling.css.jfx.validators.RadialGradientValidator;
public class JFXDialectExtension implements CssDialectExtension {
private static List<Property> PROPERTIES = new ArrayList<Property>();
private static List<Proposal> PREDEFINED_COLORS = new ArrayList<Proposal>();
static {
init();
}
private static void init() {
initColors();
PROPERTIES.addAll(Node.init());
PROPERTIES.addAll(Group.init());
PROPERTIES.addAll(ImageView.init());
PROPERTIES.addAll(ClipView.init());
PROPERTIES.addAll(Container.init());
PROPERTIES.addAll(Flow.init());
PROPERTIES.addAll(HBox.init());
PROPERTIES.addAll(Stack.init());
PROPERTIES.addAll(Tile.init());
PROPERTIES.addAll(VBox.init());
PROPERTIES.addAll(Shape.init());
PROPERTIES.addAll(Rectangle.init());
PROPERTIES.addAll(Text.init());
PROPERTIES.addAll(Region.init());
PROPERTIES.addAll(Label.init());
PROPERTIES.addAll(Labeled.init());
PROPERTIES.addAll(PasswordBox.init());
PROPERTIES.addAll(ScrollBar.init());
PROPERTIES.addAll(ScrollView.init());
PROPERTIES.addAll(Separator.init());
PROPERTIES.addAll(Slider.init());
PROPERTIES.addAll(TextBox.init());
PROPERTIES.addAll(TextInputControl.init());
PROPERTIES.addAll(AreaChart.init());
PROPERTIES.addAll(BarChart.init());
PROPERTIES.addAll(BarChart3D.init());
PROPERTIES.addAll(BubbleChart.init());
PROPERTIES.addAll(Chart.init());
PROPERTIES.addAll(LineChart.init());
PROPERTIES.addAll(PieChart.init());
PROPERTIES.addAll(PieChart3D.init());
PROPERTIES.addAll(ScatterChart.init());
PROPERTIES.addAll(XYChart.init());
PROPERTIES.addAll(Axis.init());
PROPERTIES.addAll(CategoryAxis.init());
PROPERTIES.addAll(Legend.init());
PROPERTIES.addAll(NumberAxis.init());
PROPERTIES.addAll(PlotSymbol.init());
PROPERTIES.addAll(ValueAxis.init());
PROPERTIES.addAll(MenuBar.init());
//TODO we have to add validation here
PROPERTIES.add(new Property("-fx-font") {
@Override
public List<Proposal> getInitialTermProposals() {
return Collections.emptyList();
}
});
PROPERTIES.add(new StringProperty("-fx-font-family"));
PROPERTIES.add(new SizeProperty("-fx-font-size"));
PROPERTIES.addAll(CssDialectExtension.Util.createEnumProperties(Arrays.asList("-fx-font-style"), "normal","italic","oblique"));
PROPERTIES.addAll(CssDialectExtension.Util.createEnumProperties(Arrays.asList("-fx-font-weight"), "normal","bold","bolder","lighter","100","200","300","400","500","600","700","800","900"));
}
private static void initColors() {
PREDEFINED_COLORS.add(new DialogProposal(0,"Pick color ...") {
@Override
public String openProposal() {
ColorDialog dialog = new ColorDialog(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell());
RGB rgb = dialog.open();
if( rgb != null ) {
return "rgb("+rgb.red+","+rgb.green+","+rgb.blue+")";
}
return null;
}
});
PREDEFINED_COLORS.add(new Proposal("aliceblue"));
PREDEFINED_COLORS.add(new Proposal("aqua"));
PREDEFINED_COLORS.add(new Proposal("antiquewhite"));
PREDEFINED_COLORS.add(new Proposal("aquamarine"));
PREDEFINED_COLORS.add(new Proposal("azure"));
PREDEFINED_COLORS.add(new Proposal("beige"));
PREDEFINED_COLORS.add(new Proposal("bisque"));
PREDEFINED_COLORS.add(new Proposal("black"));
PREDEFINED_COLORS.add(new Proposal("blanchedalmond"));
PREDEFINED_COLORS.add(new Proposal("blue"));
PREDEFINED_COLORS.add(new Proposal("blueviolet"));
PREDEFINED_COLORS.add(new Proposal("brown"));
PREDEFINED_COLORS.add(new Proposal("burlywood"));
PREDEFINED_COLORS.add(new Proposal("cadetblue"));
PREDEFINED_COLORS.add(new Proposal("chartreuse"));
PREDEFINED_COLORS.add(new Proposal("chocolate"));
PREDEFINED_COLORS.add(new Proposal("coral"));
PREDEFINED_COLORS.add(new Proposal("cornflowerblue"));
PREDEFINED_COLORS.add(new Proposal("cornsilk"));
PREDEFINED_COLORS.add(new Proposal("crimson"));
PREDEFINED_COLORS.add(new Proposal("cyan"));
PREDEFINED_COLORS.add(new Proposal("darkblue"));
PREDEFINED_COLORS.add(new Proposal("darkcyan"));
PREDEFINED_COLORS.add(new Proposal("darkgoldenrod"));
PREDEFINED_COLORS.add(new Proposal("darkgray"));
PREDEFINED_COLORS.add(new Proposal("darkgreen"));
PREDEFINED_COLORS.add(new Proposal("darkgrey"));
PREDEFINED_COLORS.add(new Proposal("darkkhaki"));
PREDEFINED_COLORS.add(new Proposal("darkmagenta"));
PREDEFINED_COLORS.add(new Proposal("darkolivegreen"));
PREDEFINED_COLORS.add(new Proposal("darkorange"));
PREDEFINED_COLORS.add(new Proposal("darkorchid"));
PREDEFINED_COLORS.add(new Proposal("darkred"));
PREDEFINED_COLORS.add(new Proposal("darksalmon"));
PREDEFINED_COLORS.add(new Proposal("darkseagreen"));
PREDEFINED_COLORS.add(new Proposal("darkslateblue"));
PREDEFINED_COLORS.add(new Proposal("darkslategray"));
PREDEFINED_COLORS.add(new Proposal("darkslategrey"));
PREDEFINED_COLORS.add(new Proposal("darkturquoise"));
PREDEFINED_COLORS.add(new Proposal("darkviolet"));
PREDEFINED_COLORS.add(new Proposal("deeppink"));
PREDEFINED_COLORS.add(new Proposal("deepskyblue"));
PREDEFINED_COLORS.add(new Proposal("dimgray"));
PREDEFINED_COLORS.add(new Proposal("dimgrey"));
PREDEFINED_COLORS.add(new Proposal("dodgerblue"));
PREDEFINED_COLORS.add(new Proposal("firebrick"));
PREDEFINED_COLORS.add(new Proposal("floralwhite"));
PREDEFINED_COLORS.add(new Proposal("forestgreen"));
PREDEFINED_COLORS.add(new Proposal("fuchsia"));
PREDEFINED_COLORS.add(new Proposal("gainsboro"));
PREDEFINED_COLORS.add(new Proposal("ghostwhite"));
PREDEFINED_COLORS.add(new Proposal("gold"));
PREDEFINED_COLORS.add(new Proposal("goldenrod"));
PREDEFINED_COLORS.add(new Proposal("gray"));
PREDEFINED_COLORS.add(new Proposal("green"));
PREDEFINED_COLORS.add(new Proposal("greenyellow"));
PREDEFINED_COLORS.add(new Proposal("grey"));
PREDEFINED_COLORS.add(new Proposal("honeydew"));
PREDEFINED_COLORS.add(new Proposal("hotpink"));
PREDEFINED_COLORS.add(new Proposal("indianred"));
PREDEFINED_COLORS.add(new Proposal("indigo"));
PREDEFINED_COLORS.add(new Proposal("ivory"));
PREDEFINED_COLORS.add(new Proposal("khaki"));
PREDEFINED_COLORS.add(new Proposal("lavender"));
PREDEFINED_COLORS.add(new Proposal("lavenderblush"));
PREDEFINED_COLORS.add(new Proposal("lawngreen"));
PREDEFINED_COLORS.add(new Proposal("lemonchiffon"));
PREDEFINED_COLORS.add(new Proposal("lightblue"));
PREDEFINED_COLORS.add(new Proposal("lightcoral"));
PREDEFINED_COLORS.add(new Proposal("lightcyan"));
PREDEFINED_COLORS.add(new Proposal("lightgoldenrodyellow"));
PREDEFINED_COLORS.add(new Proposal("lightgray"));
PREDEFINED_COLORS.add(new Proposal("lightgreen"));
PREDEFINED_COLORS.add(new Proposal("lightgrey"));
PREDEFINED_COLORS.add(new Proposal("lightpink"));
PREDEFINED_COLORS.add(new Proposal("lightsalmon"));
PREDEFINED_COLORS.add(new Proposal("lightseagreen"));
PREDEFINED_COLORS.add(new Proposal("lightskyblue"));
PREDEFINED_COLORS.add(new Proposal("lightslategray"));
PREDEFINED_COLORS.add(new Proposal("lightslategrey"));
PREDEFINED_COLORS.add(new Proposal("lightsteelblue"));
PREDEFINED_COLORS.add(new Proposal("lightyellow"));
PREDEFINED_COLORS.add(new Proposal("lime"));
PREDEFINED_COLORS.add(new Proposal("limegreen"));
PREDEFINED_COLORS.add(new Proposal("linen"));
PREDEFINED_COLORS.add(new Proposal("magenta"));
PREDEFINED_COLORS.add(new Proposal("maroon"));
PREDEFINED_COLORS.add(new Proposal("mediumaquamarine"));
PREDEFINED_COLORS.add(new Proposal("mediumblue"));
PREDEFINED_COLORS.add(new Proposal("mediumorchid"));
PREDEFINED_COLORS.add(new Proposal("mediumpurple"));
PREDEFINED_COLORS.add(new Proposal("mediumseagreen"));
PREDEFINED_COLORS.add(new Proposal("mediumslateblue"));
PREDEFINED_COLORS.add(new Proposal("mediumspringgreen"));
PREDEFINED_COLORS.add(new Proposal("mediumturquoise"));
PREDEFINED_COLORS.add(new Proposal("mediumvioletred"));
PREDEFINED_COLORS.add(new Proposal("midnightblue"));
PREDEFINED_COLORS.add(new Proposal("mintcream"));
PREDEFINED_COLORS.add(new Proposal("mistyrose"));
PREDEFINED_COLORS.add(new Proposal("moccasin"));
PREDEFINED_COLORS.add(new Proposal("navajowhite"));
PREDEFINED_COLORS.add(new Proposal("navy"));
PREDEFINED_COLORS.add(new Proposal("oldlace"));
PREDEFINED_COLORS.add(new Proposal("olive"));
PREDEFINED_COLORS.add(new Proposal("olivedrab"));
PREDEFINED_COLORS.add(new Proposal("orange"));
PREDEFINED_COLORS.add(new Proposal("orangered"));
PREDEFINED_COLORS.add(new Proposal("orchid"));
PREDEFINED_COLORS.add(new Proposal("palegoldenrod"));
PREDEFINED_COLORS.add(new Proposal("palegreen"));
PREDEFINED_COLORS.add(new Proposal("paleturquoise"));
PREDEFINED_COLORS.add(new Proposal("palevioletred"));
PREDEFINED_COLORS.add(new Proposal("papayawhip"));
PREDEFINED_COLORS.add(new Proposal("peachpuff"));
PREDEFINED_COLORS.add(new Proposal("peru"));
PREDEFINED_COLORS.add(new Proposal("pink"));
PREDEFINED_COLORS.add(new Proposal("plum"));
PREDEFINED_COLORS.add(new Proposal("powderblue"));
PREDEFINED_COLORS.add(new Proposal("purple"));
PREDEFINED_COLORS.add(new Proposal("red"));
PREDEFINED_COLORS.add(new Proposal("rosybrown"));
PREDEFINED_COLORS.add(new Proposal("royalblue"));
PREDEFINED_COLORS.add(new Proposal("saddlebrown"));
PREDEFINED_COLORS.add(new Proposal("salmon"));
PREDEFINED_COLORS.add(new Proposal("sandybrown"));
PREDEFINED_COLORS.add(new Proposal("seagreen"));
PREDEFINED_COLORS.add(new Proposal("seashell"));
PREDEFINED_COLORS.add(new Proposal("sienna"));
PREDEFINED_COLORS.add(new Proposal("silver"));
PREDEFINED_COLORS.add(new Proposal("skyblue"));
PREDEFINED_COLORS.add(new Proposal("slateblue"));
PREDEFINED_COLORS.add(new Proposal("slategray"));
PREDEFINED_COLORS.add(new Proposal("slategrey"));
PREDEFINED_COLORS.add(new Proposal("snow"));
PREDEFINED_COLORS.add(new Proposal("springgreen"));
PREDEFINED_COLORS.add(new Proposal("steelblue"));
PREDEFINED_COLORS.add(new Proposal("tan"));
PREDEFINED_COLORS.add(new Proposal("teal"));
PREDEFINED_COLORS.add(new Proposal("thistle"));
PREDEFINED_COLORS.add(new Proposal("tomato"));
PREDEFINED_COLORS.add(new Proposal("turquoise"));
PREDEFINED_COLORS.add(new Proposal("violet"));
PREDEFINED_COLORS.add(new Proposal("wheat"));
PREDEFINED_COLORS.add(new Proposal("white"));
PREDEFINED_COLORS.add(new Proposal("whitesmoke"));
PREDEFINED_COLORS.add(new Proposal("yellow"));
PREDEFINED_COLORS.add(new Proposal("yellowgreen"));
PREDEFINED_COLORS.add(new Proposal("transparent"));
}
@Override
public List<Property> getProperties() {
return PROPERTIES;
}
public static class SizeProperty extends Property {
private List<Proposal> proposals = new ArrayList<Proposal>();
public SizeProperty(String name) {
super(name);
proposals.add(new Proposal("1"));
for( String u : sizeUnits() ) {
proposals.add(new Proposal("1"+u));
}
}
@Override
public List<Proposal> getInitialTermProposals() {
return proposals;
}
@Override
public ValidationResult[] validate(css_generic_declaration dec) {
if( dec.getExpression() != null ) {
if( dec.getExpression().getTermGroups().size() > 1 ) {
return new ValidationResult[] { new ValidationResult(ValidationStatus.ERROR, "The attribute does not support multiple groups", dec, CssDslPackage.Literals.CSS_GENERIC_DECLARATION__EXPRESSION, -1) };
} else if( dec.getExpression().getTermGroups().size() == 1 ) {
termGroup g = dec.getExpression().getTermGroups().get(0);
if( g.getTerms().size() == 1 ) {
List<ValidationResult> rv = new ArrayList<ValidationResult>();
for( term t : g.getTerms() ) {
if( t.getNumber() == null ) {
rv.add(new ValidationResult(ValidationStatus.ERROR, "The value must be a size", t, null, -1));
continue;
}
// Number with units
if( ! Pattern.matches(".*\\d+$",t.getNumber()) ) {
for( String u : sizeUnits() ) {
if( t.getNumber().endsWith(u) ) {
return super.validate(dec);
}
}
StringBuilder b = new StringBuilder();
b.append("- <none>\n");
for( String p: sizeUnits() ) {
b.append("- " + p + "\n");
}
rv.add(new ValidationResult(ValidationStatus.ERROR, "Supported units are:\n"+b, t, CssDslPackage.Literals.TERM__NUMBER, -1));
}
}
if( rv.size() != 0 ) {
return rv.toArray(new ValidationResult[0]);
}
} else if( g.getTerms().size() > 1 ) {
return new ValidationResult[] { new ValidationResult(ValidationStatus.ERROR, "The attribute only supports one size value", dec, CssDslPackage.Literals.CSS_GENERIC_DECLARATION__EXPRESSION, -1) };
}
}
}
return super.validate(dec);
}
}
public static class Size4TimesProperty extends Property {
private List<Proposal> proposals = new ArrayList<Proposal>();
public Size4TimesProperty(String name) {
super(name);
proposals.add(new Proposal("1"));
proposals.add(new Proposal("1 1 1 1"));
for( String u : sizeUnits() ) {
proposals.add(new Proposal("1"+u));
proposals.add(new Proposal("1"+u+" 1"+u+" 1"+u+" 1"+u));
}
}
@Override
public List<Proposal> getInitialTermProposals() {
return proposals;
}
@Override
public ValidationResult[] validate(css_generic_declaration dec) {
if( dec.getExpression() != null ) {
if( dec.getExpression().getTermGroups().size() > 1 ) {
return new ValidationResult[] { new ValidationResult(ValidationStatus.ERROR, "The attribute does not support multiple groups", dec, CssDslPackage.Literals.CSS_GENERIC_DECLARATION__EXPRESSION, -1) };
} else if( dec.getExpression().getTermGroups().size() == 1 ) {
termGroup g = dec.getExpression().getTermGroups().get(0);
if( g.getTerms().size() == 1 || g.getTerms().size() == 4 ) {
List<ValidationResult> rv = new ArrayList<ValidationResult>();
for( term t : g.getTerms() ) {
if( t.getNumber() == null ) {
rv.add(new ValidationResult(ValidationStatus.ERROR, "The value must be a size", t, null, -1));
continue;
}
// Number with units
if( ! Pattern.matches(".*\\d+$",t.getNumber()) ) {
for( String u : sizeUnits() ) {
if( t.getNumber().endsWith(u) ) {
return super.validate(dec);
}
}
StringBuilder b = new StringBuilder();
b.append("- <none>\n");
for( String p: sizeUnits() ) {
b.append("- " + p + "\n");
}
rv.add(new ValidationResult(ValidationStatus.ERROR, "Supported units are:\n"+b, t, CssDslPackage.Literals.TERM__NUMBER, -1));
}
}
if( rv.size() != 0 ) {
return rv.toArray(new ValidationResult[0]);
}
} else if( g.getTerms().size() > 1 && g.getTerms().size() != 4 ) {
return new ValidationResult[] { new ValidationResult(ValidationStatus.ERROR, "The attribute only supports 1 or 4 sizes", g, null, -1) };
}
}
}
return super.validate(dec);
}
}
public static class MultiSize4TimesProperty extends Property implements MultiTermGroupProperty, MultiValuesGroupProperty {
private List<Proposal> proposals = new ArrayList<Proposal>();
private List<Proposal> singleTerm = new ArrayList<Proposal>();
public MultiSize4TimesProperty(String name) {
super(name);
proposals.add(new Proposal("1"));
singleTerm.add(new Proposal("1"));
proposals.add(new Proposal("1 1 1 1"));
for( String u : sizeUnits() ) {
proposals.add(new Proposal("1"+u));
singleTerm.add(new Proposal("1"+u));
proposals.add(new Proposal("1"+u+" 1"+u+" 1"+u+" 1"+u));
}
}
@Override
public List<Proposal> getInitialTermProposals() {
return proposals;
}
@Override
public List<Proposal> getNextTermProposal(int index,
termGroup group, term term) {
if( index < 4 ) {
return singleTerm;
}
return Collections.emptyList();
}
@Override
public List<Proposal> getInitialTermProposal(int index,
css_declaration currentDeclaration) {
return proposals;
}
@Override
public ValidationResult[] validate(css_generic_declaration dec) {
if( dec.getExpression() != null ) {
for( termGroup g : dec.getExpression().getTermGroups() ) {
if( g.getTerms().size() == 1 || g.getTerms().size() == 4 ) {
List<ValidationResult> rv = new ArrayList<ValidationResult>();
for( term t : g.getTerms() ) {
if( t.getNumber() == null ) {
rv.add(new ValidationResult(ValidationStatus.ERROR, "The value must be a size", t, null, -1));
continue;
}
// Number with units
if( ! Pattern.matches(".*\\d+$",t.getNumber()) ) {
for( String u : sizeUnits() ) {
if( t.getNumber().endsWith(u) ) {
return super.validate(dec);
}
}
StringBuilder b = new StringBuilder();
b.append("- <none>\n");
for( String p: sizeUnits() ) {
b.append("- " + p + "\n");
}
rv.add(new ValidationResult(ValidationStatus.ERROR, "Supported units are:\n"+b, t, CssDslPackage.Literals.TERM__NUMBER, -1));
}
}
if( rv.size() != 0 ) {
return rv.toArray(new ValidationResult[0]);
}
} else if( g.getTerms().size() > 1 && g.getTerms().size() != 4 ) {
return new ValidationResult[] { new ValidationResult(ValidationStatus.ERROR, "The attribute only supports 1 or 4 sizes", g, null, -1) };
}
}
}
return super.validate(dec);
}
}
public static class PaintProperty extends Property {
private List<Proposal> proposals = new ArrayList<Proposal>();
public PaintProperty(String name) {
super(name);
// color stuff
proposals.addAll(PREDEFINED_COLORS);
proposals.add(new Proposal(2,"#000"));
proposals.add(new Proposal(2,"#00000"));
proposals.add(new Proposal(2,"rgb(0,0,0)"));
proposals.add(new Proposal(2,"rgba(0,0,0,0)"));
proposals.add(new Proposal(2,"hsb(0,0%,0%)"));
proposals.add(new Proposal(2,"hsba(0,0%,0%,0)"));
proposals.add(new Proposal(2,"derive(<color>,0%)"));
proposals.add(new Proposal(2,"ladder(<color>) stops (0, <color>)"));
// gradient
proposals.add(new Proposal(2,"linear-gradient( from 0% 0% to 100% 100%, repeat, 0% red, 100% black )"));
proposals.add(new Proposal(2,"radial <size> stops ( <number> , <color> )"));
}
@Override
public List<Proposal> getInitialTermProposals() {
return proposals;
}
@Override
public ValidationResult[] validate(css_generic_declaration dec) {
if( dec.getExpression() != null ) {
if( dec.getExpression().getTermGroups().size() > 1 ) {
return new ValidationResult[] { new ValidationResult(ValidationStatus.ERROR, "The attribute does not support multiple groups", dec, CssDslPackage.Literals.CSS_GENERIC_DECLARATION__EXPRESSION, -1) };
} else if( dec.getExpression().getTermGroups().size() == 1 ) {
List<ValidationResult> list = new ArrayList<ValidationResult>();
if( dec.getExpression().getTermGroups().get(0).getTerms().size() == 1 ) {
if( isGradient(dec.getExpression().getTermGroups().get(0).getTerms().get(0), list) ) {
return list.toArray(new ValidationResult[0]);
} else {
validateColor(dec.getExpression().getTermGroups().get(0).getTerms().get(0),list);
if( list.size() > 0 ) {
return list.toArray(new ValidationResult[0]);
}
}
}
}
}
return super.validate(dec);
}
}
public static class MultiPaintProperty extends Property implements MultiTermGroupProperty {
private List<Proposal> proposals = new ArrayList<Proposal>();
public MultiPaintProperty(String name) {
super(name);
proposals.addAll(PREDEFINED_COLORS);
proposals.add(new Proposal(2,"#000"));
proposals.add(new Proposal(2,"#00000"));
proposals.add(new Proposal(2,"rgb(0,0,0)"));
proposals.add(new Proposal(2,"rgba(0,0,0,0)"));
proposals.add(new Proposal(2,"hsb(0,0%,0%)"));
proposals.add(new Proposal(2,"hsb(0,0%,0%,0)"));
proposals.add(new Proposal(2,"derive(<color>,0%)"));
proposals.add(new Proposal(2,"ladder(<color>) stops (0, <color>)"));
// gradient
proposals.add(new Proposal(2,"linear-gradient( from 0% 0% to 100% 100%, repeat, 0% red, 100% black )"));
proposals.add(new Proposal(2,"radial <size> stops ( <number> , <color> )"));
}
@Override
public List<Proposal> getInitialTermProposals() {
return proposals;
}
@Override
public List<Proposal> getInitialTermProposal(int index,
css_declaration currentDeclaration) {
return proposals;
}
@Override
public ValidationResult[] validate(css_generic_declaration dec) {
List<ValidationResult> rv = new ArrayList<CssDialectExtension.ValidationResult>();
if( dec.getExpression() != null ) {
for( termGroup g : dec.getExpression().getTermGroups() ) {
if( g.getTerms().size() == 1 ) {
if( ! isGradient(g.getTerms().get(0), rv) ) {
validateColor(g.getTerms().get(0), rv);
}
} else {
rv.add(new ValidationResult(ValidationStatus.ERROR, "The property allows only one term", g, null, -1));
}
}
}
return rv.toArray(new ValidationResult[0]);
}
}
public static class MultiPaint4TimesProperty extends Property implements MultiValuesGroupProperty, MultiTermGroupProperty {
private List<Proposal> proposals = new ArrayList<Proposal>();
public MultiPaint4TimesProperty(String name) {
super(name);
proposals.addAll(PREDEFINED_COLORS);
proposals.add(new Proposal(2,"#000"));
proposals.add(new Proposal(2,"#00000"));
proposals.add(new Proposal(2,"rgb(0,0,0)"));
proposals.add(new Proposal(2,"rgba(0,0,0,0)"));
proposals.add(new Proposal(2,"hsb(0,0%,0%)"));
proposals.add(new Proposal(2,"hsb(0,0%,0%,0)"));
proposals.add(new Proposal(2,"derive(<color>,0%)"));
proposals.add(new Proposal(2,"ladder(<color>) stops (0, <color>)"));
// gradient
proposals.add(new Proposal(2,"linear-gradient( from 0% 0% to 100% 100%, repeat, 0% red, 100% black )"));
// proposals.add(new Proposal(2,"radial <size> stops ( <number> , <color> )"));
}
@Override
public List<Proposal> getInitialTermProposals() {
return proposals;
}
@Override
public List<Proposal> getInitialTermProposal(int index,
css_declaration currentDeclaration) {
return proposals;
}
@Override
public List<Proposal> getNextTermProposal(int index,
termGroup group, term term) {
return proposals;
}
@Override
public ValidationResult[] validate(css_generic_declaration dec) {
List<ValidationResult> rv = new ArrayList<CssDialectExtension.ValidationResult>();
if( dec.getExpression() != null ) {
for( termGroup g : dec.getExpression().getTermGroups() ) {
if( g.getTerms().size() == 1 || g.getTerms().size() == 4 ) {
for( term t : g.getTerms() ) {
if( ! isGradient(t, rv) ) {
validateColor(g.getTerms().get(0), rv);
}
}
} else {
rv.add(new ValidationResult(ValidationStatus.ERROR, "The group has to have 1 or 4 values", g, null, -1));
}
}
}
return rv.toArray(new ValidationResult[0]);
}
}
public static class Number4TimesProperty extends Property implements MultiValuesGroupProperty {
private List<Proposal> proposals = new ArrayList<Proposal>();
public Number4TimesProperty(String name) {
super(name);
proposals.add(new Proposal("10"));
proposals.add(new Proposal("10 10 10 10"));
}
@Override
public List<Proposal> getInitialTermProposals() {
return proposals;
}
@Override
public List<Proposal> getNextTermProposal(int index,
termGroup currentGroup, term term) {
if( index < 4 ) {
return Util.fromList("0");
}
return Collections.emptyList();
}
@Override
public ValidationResult[] validate(css_generic_declaration dec) {
if( dec.getExpression() != null ) {
if( dec.getExpression().getTermGroups().size() > 1 ) {
return new ValidationResult[] { new ValidationResult(ValidationStatus.ERROR, "The attribute does not support multiple groups", dec, CssDslPackage.Literals.CSS_GENERIC_DECLARATION__EXPRESSION, -1) };
} else if( dec.getExpression().getTermGroups().size() == 1 ) {
termGroup g = dec.getExpression().getTermGroups().get(0);
if( g.getTerms().size() == 1 ) {
ValidationResult r = Util.checkNumber(g.getTerms().get(0), "The attribute has to be a number");
if( r == null ) {
return super.validate(dec);
}
} else if( g.getTerms().size() == 4 ) {
List<ValidationResult> rList = new ArrayList<ValidationResult>();
for( term t : g.getTerms() ) {
ValidationResult r = Util.checkNumber(t, "The attribute has to be a number");
if( r == null ) {
rList.add(r);
}
}
if( ! rList.isEmpty() ) {
return rList.toArray(new ValidationResult[0]);
}
} else {
return new ValidationResult[] { new ValidationResult(ValidationStatus.ERROR, "The attribute has to be 1 or 4 value", dec, CssDslPackage.Literals.CSS_GENERIC_DECLARATION__EXPRESSION, -1) };
}
}
}
return super.validate(dec);
}
}
public static class FontProperty extends Property {
private List<Proposal> proposals = new ArrayList<Proposal>();
public FontProperty(String name) {
super(name);
}
@Override
public List<Proposal> getInitialTermProposals() {
return proposals;
}
}
public static class FontFamilyProperty extends Property {
private List<Proposal> proposals = new ArrayList<Proposal>();
public FontFamilyProperty(String name) {
super(name);
}
@Override
public List<Proposal> getInitialTermProposals() {
return proposals;
}
}
public static class FontSizeProperty extends Property {
private List<Proposal> proposals = new ArrayList<Proposal>();
public FontSizeProperty(String name) {
super(name);
}
@Override
public List<Proposal> getInitialTermProposals() {
return proposals;
}
}
public static class FontStyleProperty extends Property {
private List<Proposal> proposals = new ArrayList<Proposal>();
public FontStyleProperty(String name) {
super(name);
proposals.addAll(CssDialectExtension.Util.fromList("normal","italic","oblique"));
}
@Override
public List<Proposal> getInitialTermProposals() {
return proposals;
}
}
public static class FontWeightProperty extends Property {
private List<Proposal> proposals = new ArrayList<Proposal>();
public FontWeightProperty(String name) {
super(name);
proposals.addAll(CssDialectExtension.Util.fromList("normal","bold","bolder","lighter","100","200","300","400","500","600","700","800","900"));
}
@Override
public List<Proposal> getInitialTermProposals() {
return proposals;
}
}
public static class FxNumberProperty extends NumberPropery {
public FxNumberProperty(String name) {
super(name);
}
@Override
public ValidationResult[] validate(css_generic_declaration dec) {
if( isReference(dec) ) {
return new ValidationResult[0];
}
return super.validate(dec);
}
}
public static List<String> sizeUnits() {
return Arrays.asList("px","mm","cm","in","pt","pc","em","ex");
}
public static List<String> angles() {
return Arrays.asList("grad","deg","rad"); // order is important!!!
}
public static boolean isReference(css_generic_declaration dec) {
if( dec.getExpression().getTermGroups().size() == 1 ) {
termGroup g = dec.getExpression().getTermGroups().get(0);
if( g.getTerms().size() == 1 ) {
String identifier = g.getTerms().get(0).getIdentifier();
//TODO We should get smarter in future and check the referenced value
if( identifier != null && identifier.startsWith("-fx-") ) {
return true;
}
}
}
return false;
}
public static boolean isGradient(term t, List<ValidationResult> list) {
if( t.getFunction() != null && "linear-gradient".equals(t.getFunction().getName()) ) {
LinearGradientValidator.validateLinearGradient(t.getFunction(), list);
return true;
} else if( t.getFunction() != null && "radial-gradient".equals(t.getFunction().getName()) ) {
RadialGradientValidator.validateRadialGradient(t.getFunction(), list);
return true;
}
return false;
}
public static void validateSize(term term, List<ValidationResult> list) {
if( term.getNumber() == null ) {
list.add(new ValidationResult(ValidationStatus.ERROR, "The value must be a size", term, null, -1));
return;
}
if( term.getNumber().trim().endsWith("%") ) {
validatePercentage(term, list);
} else if( ! Pattern.matches(".*\\d+$",term.getNumber()) ) {
// Number with units
for( String u : sizeUnits() ) {
if( term.getNumber().endsWith(u) ) {
String num = term.getNumber().substring(0,term.getNumber().length() - u.length());
try {
Double.parseDouble(num);
} catch (NumberFormatException e) {
list.add(new ValidationResult(ValidationStatus.ERROR, "The angle is not a valid number", term, CssDslPackage.Literals.TERM__NUMBER, 0));
}
return;
}
}
StringBuilder b = new StringBuilder();
b.append("- <none>\n");
for( String p: sizeUnits() ) {
b.append("- " + p + "\n");
}
list.add(new ValidationResult(ValidationStatus.ERROR, "Supported units are:\n"+b, term, CssDslPackage.Literals.TERM__NUMBER, -1));
}
}
private static boolean isHexDigit(char c) {
return (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || (c >= '0' && c <= '9');
}
public static void validateColor(term t, List<ValidationResult> list) {
if( t.getIdentifier() != null ) {
for( Proposal color: PREDEFINED_COLORS ) {
if( t.getIdentifier().equalsIgnoreCase(color.getProposal()) ) {
return;
}
}
if( ! t.getIdentifier().startsWith("-") ) {
list.add(new ValidationResult(ValidationStatus.ERROR, "'"+t.getIdentifier()+"' is not a known color", t, CssDslPackage.Literals.TERM__IDENTIFIER, 0));
}
} else if( t.getHexColor() != null ) {
if( !(t.getHexColor().length() == 4 || t.getHexColor().length() == 7 || t.getHexColor().length() == 9) ) {
list.add(new ValidationResult(ValidationStatus.ERROR, "A hex-color definition has to have 3, 6 or 9 hex-digits", t, CssDslPackage.Literals.TERM__HEX_COLOR, -1));
} else {
for( char c : t.getHexColor().substring(1).toCharArray() ) {
if( ! isHexDigit(c) ) {
list.add(new ValidationResult(ValidationStatus.ERROR, "'"+c+"' is not a valid digit in hexcolors", t, CssDslPackage.Literals.TERM__HEX_COLOR, -1));
break;
}
}
}
return;
} else if( t.getFunction() != null ) {
if( "rgb".equals(t.getFunction().getName()) ) {
expr e = t.getFunction().getExpression();
if( e != null && e.getTermGroups().size() == 3 ) {
for( termGroup g : e.getTermGroups() ) {
if( g.getTerms().size() == 1 ) {
term colorterm = g.getTerms().get(0);
if( colorterm.getNumber() != null ) {
if( colorterm.getNumber().matches("^\\d+$") ) {
int v = Integer.parseInt(colorterm.getNumber());
if( v < 0 || v > 255 ) {
list.add(new ValidationResult(ValidationStatus.ERROR, "The value has to be an integer between 0 and 255", colorterm, null, -1));
}
} else {
list.add(new ValidationResult(ValidationStatus.ERROR, "The value has to be an integer between 0 and 255", colorterm, null, -1));
}
} else {
list.add(new ValidationResult(ValidationStatus.ERROR, "The value has to be a single number", colorterm, null, -1));
}
} else {
list.add(new ValidationResult(ValidationStatus.ERROR, "The value has to be a single number", g, null, -1));
}
}
} else {
list.add(new ValidationResult(ValidationStatus.ERROR, "A RGB value has to be defined with 3 values (red,green,blue)", t.getFunction(), null, -1));
}
} else if( "rgba".equals(t.getFunction().getName()) ) {
expr e = t.getFunction().getExpression();
if( e != null && e.getTermGroups().size() == 4 ) {
for( int i = 0; i < 3; i++ ) {
termGroup g = e.getTermGroups().get(i);
if( g.getTerms().size() == 1 ) {
term colorterm = g.getTerms().get(0);
if( colorterm.getNumber() != null ) {
if( colorterm.getNumber().matches("^\\d+$") ) {
int v = Integer.parseInt(colorterm.getNumber());
if( v < 0 || v > 255 ) {
list.add(new ValidationResult(ValidationStatus.ERROR, "The value has to be an integer between 0 and 255", colorterm, null, -1));
}
} else {
list.add(new ValidationResult(ValidationStatus.ERROR, "The value has to be an integer between 0 and 255", colorterm, null, -1));
}
} else {
list.add(new ValidationResult(ValidationStatus.ERROR, "The value has to be a single number", colorterm, null, -1));
}
} else {
list.add(new ValidationResult(ValidationStatus.ERROR, "The value has to be a single number", g, null, -1));
}
}
termGroup g = e.getTermGroups().get(3);
if( g.getTerms().size() == 1 ) {
try {
double v = Double.parseDouble(g.getTerms().get(0).getNumber());
if( v < 0 || v > 1.0 ) {
list.add(new ValidationResult(ValidationStatus.ERROR, "The value has to be a single floating point number between 0.0 (=transparent) and 1.0 (=opaque)", g, null, -1));
}
} catch (Exception ex) {
list.add(new ValidationResult(ValidationStatus.ERROR, "The value has to be a single floating point number between 0.0 (=transparent) and 1.0 (=opaque)", g, null, -1));
}
} else {
list.add(new ValidationResult(ValidationStatus.ERROR, "The value has to be a single floating point number between 0.0 (=transparent) and 1.0 (=opaque)", g, null, -1));
}
} else {
list.add(new ValidationResult(ValidationStatus.ERROR, "A RGB value has to be defined with 3 values (red,green,blue)", t.getFunction(), null, -1));
}
} else if( "hsb".equals(t.getFunction().getName()) || "hsba".equals(t.getFunction().getName()) ) {
expr e = t.getFunction().getExpression();
if( e != null && (( e.getTermGroups().size() == 3 && "hsb".equals(t.getFunction().getName()) ||
( e.getTermGroups().size() == 4 && "hsba".equals(t.getFunction().getName())) ))) {
{
termGroup g = e.getTermGroups().get(0);
if( g.getTerms().size() == 1 ) {
term term = g.getTerms().get(0);
if( term.getNumber() != null ) {
if( term.getNumber().matches("^\\d+$") ) {
int v = Integer.parseInt(term.getNumber());
if( v < 0 || v > 360 ) {
list.add(new ValidationResult(ValidationStatus.ERROR, "The hue value has to be an integer between 0 and 360", term, null, -1));
}
} else {
list.add(new ValidationResult(ValidationStatus.ERROR, "The hue value has to be an integer between 0 and 360", term, null, -1));
}
} else {
list.add(new ValidationResult(ValidationStatus.ERROR, "The hue value has to be an integer number", term, null, -1));
}
} else {
list.add(new ValidationResult(ValidationStatus.ERROR, "The hue value has to be a single number", g, null, -1));
}
}
{
termGroup g = e.getTermGroups().get(1);
if( g.getTerms().size() == 1 ) {
term term = g.getTerms().get(0);
if( term.getNumber() != null ) {
ValidationResult res = Util.checkPercentage(term, "The saturation value has to be an integer between 0% and 100%",0);
if( res != null ) {
list.add(res);
}
} else {
list.add(new ValidationResult(ValidationStatus.ERROR, "The saturation value has to be an integer number", term, null, -1));
}
} else {
list.add(new ValidationResult(ValidationStatus.ERROR, "The saturation value has to be a single number", g, null, -1));
}
}
{
termGroup g = e.getTermGroups().get(2);
if( g.getTerms().size() == 1 ) {
term term = g.getTerms().get(0);
if( term.getNumber() != null ) {
ValidationResult res = Util.checkPercentage(term, "The brightness value has to be an integer between 0% and 100%",0);
if( res != null ) {
list.add(res);
}
} else {
list.add(new ValidationResult(ValidationStatus.ERROR, "The brightness value has to be an integer number", term, null, -1));
}
} else {
list.add(new ValidationResult(ValidationStatus.ERROR, "The brightness value has to be a single number", g, null, -1));
}
}
if( "hsba".equals(t.getFunction().getName()) ) {
termGroup g = e.getTermGroups().get(3);
if( g.getTerms().size() == 1 ) {
if( g.getTerms().get(0).getNumber().matches(".+\\d$") ) {
double v = Double.parseDouble(g.getTerms().get(0).getNumber());
if( v < 0 || v > 1.0 ) {
list.add(new ValidationResult(ValidationStatus.ERROR, "The value has to be a single floating point number between 0.0 (=transparent) and 1.0 (=opaque)", g, null, -1));
}
} else {
list.add(new ValidationResult(ValidationStatus.ERROR, "The value has to be a single floating point number between 0.0 (=transparent) and 1.0 (=opaque)", g, null, -1));
}
} else {
list.add(new ValidationResult(ValidationStatus.ERROR, "The value has to be a single floating point number between 0.0 (=transparent) and 1.0 (=opaque)", g, null, -1));
}
}
} else {
if( "hsb".equals(t.getFunction().getName()) ) {
list.add(new ValidationResult(ValidationStatus.ERROR, "A HSB value has to be defined with 3 values (hue,saturation,brightness)", t.getFunction(), null, -1));
} else {
list.add(new ValidationResult(ValidationStatus.ERROR, "A HSB value has to be defined with 4 values (hue,saturation,brightness,alpha)", t.getFunction(), null, -1));
}
}
} else if( "derive".equals(t.getFunction().getName()) ) {
expr e = t.getFunction().getExpression();
if( e.getTermGroups().size() == 2 ) {
if( e.getTermGroups().get(0).getTerms().size() == 1 ) {
validateColor(e.getTermGroups().get(0).getTerms().get(0), list);
termGroup g = e.getTermGroups().get(1);
if( g.getTerms().size() == 1 ) {
term term = g.getTerms().get(0);
if( term.getNumber() != null ) {
ValidationResult res = Util.checkPercentage(term, "The brightness value has to be an integer between -100% and 100%",-100);
if( res != null ) {
list.add(res);
}
} else {
list.add(new ValidationResult(ValidationStatus.ERROR, "The brightness value has to be an integer number", term, null, -1));
}
} else {
list.add(new ValidationResult(ValidationStatus.ERROR, "The brightness value has to be a single number", g, null, -1));
}
} else {
list.add(new ValidationResult(ValidationStatus.ERROR, "The brightness-color has to be a single color", e.getTermGroups().get(0), null, -1));
}
} else {
list.add(new ValidationResult(ValidationStatus.ERROR, "The derive function has to be passed 2 arguments (color,brightness)", t.getFunction(), null, -1));
}
} else if( "ladder".equals(t.getFunction().getName()) ) {
expr e = t.getFunction().getExpression();
if( e.getTermGroups().size() >= 2 ) {
termGroup colorGroup = e.getTermGroups().get(0);
validateColor(colorGroup.getTerms().get(0), list);
for( int i = 1; i < e.getTermGroups().size(); i++ ) {
GradientValidator.validateColorStop(e.getTermGroups().get(i), list);
}
} else {
list.add(new ValidationResult(ValidationStatus.ERROR, "The ladder function needs at least 2 arguments. A color and color-stop", e.eContainer(), CssDslPackage.Literals.FUNCTION__EXPRESSION, -1));
}
} else {
list.add(new ValidationResult(ValidationStatus.ERROR, "Unsupported color function", t.getFunction(), null, -1));
}
} else {
list.add(new ValidationResult(ValidationStatus.ERROR, "Unsupported color definition", t, null, -1));
}
}
public static void validateAngle(term term, List<ValidationResult> list) {
if( term.getNumber() == null ) {
list.add(new ValidationResult(ValidationStatus.ERROR, "The value must be an angle", term, null, -1));
return;
}
for( String a : angles() ) {
if( term.getNumber().endsWith(a) ) {
String num = term.getNumber().substring(0,term.getNumber().length() - a.length());
try {
Double.parseDouble(num);
} catch (NumberFormatException e) {
list.add(new ValidationResult(ValidationStatus.ERROR, "The angle is not a valid number", term, CssDslPackage.Literals.TERM__NUMBER, -1));
}
return;
}
}
StringBuilder b = new StringBuilder();
for( String a: angles() ) {
b.append("- " + a + "\n");
}
list.add(new ValidationResult(ValidationStatus.ERROR, "Supported angle units are:\n"+b, term, CssDslPackage.Literals.TERM__NUMBER, -1));
}
public static void validatePercentage(term term, List<ValidationResult> list) {
if( term.getNumber() == null || ! term.getNumber().endsWith("%") ) {
list.add(new ValidationResult(ValidationStatus.ERROR, "The value must be a percentage", term, CssDslPackage.Literals.TERM__NUMBER, -1));
return;
}
double i = Double.parseDouble(term.getNumber().substring(0,term.getNumber().length()-1));
if( i < 0 || i > 100 ) {
list.add(new ValidationResult(ValidationStatus.ERROR, "Percentage has to be between 0% and 100%", term, CssDslPackage.Literals.TERM__NUMBER, -1));
}
}
public boolean isActive(URI uri) {
return uri.toString().endsWith(".css");
};
}