com.codename1.ui.plaf.StyleParser Maven / Gradle / Ivy
/*
* Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Codename One through http://www.codenameone.com/ if you
* need additional information or have any questions.
*/
package com.codename1.ui.plaf;
import com.codename1.io.Util;
import com.codename1.l10n.L10NManager;
import com.codename1.ui.Component;
import com.codename1.ui.Display;
import com.codename1.ui.Font;
import com.codename1.ui.Image;
import com.codename1.ui.util.Resources;
import com.codename1.util.CaseInsensitiveOrder;
import com.codename1.util.StringUtil;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Parses Style strings into StyleInfo objects, which can be converted to Style objects at runtime.
* This class
* is the basis for the inline style functionality:
*
* - {@link Component#setInlineAllStyles(java.lang.String) }
* - {@link Component#getInlineAllStyles() }
* - {@link Component#setInlineSelectedStyles(java.lang.String) }
* - {@link Component#getInlineSelectedStyles() }
* - etc..
*
*
* Style strings are strings which describe a style, and are in a particular format that {@link StyleParser} knows how to
* parse. The general format of a style string is {@literal key1:value1; key2:value2; ... keyn:valuen;}. I.e. a set of key-value pairs
* with pairs separated by semi-colons, and keys and values separated by a colon. This is very similar to CSS, but it is NOT CSS.
* Style string keys and values are closely related to the properties of the {@link Style} class and their associated values.
*
* Supported Keys
*
* The following keys are supported:
*
* - {@literal fgColor} - The foreground color as a hex string. E.g. {@literal ff0000}.
* - {@literal bgColor} - The background color as a hex string. E.g. {@literal ff0000}.
* - {@literal transparency} - The background transparency as an integer. 0-255.
* - {@literal textDecoration} - The text decoration. One of {@literal underline}, {@literal overline},
* {@literal 3d}, {@literal 3d_lowered}, {@literal 3d_shadow_north}, {@literal strikethru}, or {@literal none}.
* - {@literal opacity} - The opacity as an integer. 0-255
* - {@literal padding} - The padding as a sequence of 1, 2, 3, or 4 values. See "Padding and Margin Strings" below for details on the format.
* - {@literal margin} - The margin as a sequence of 1, 2, 3, or 4, values. See "Padding and Margin Strings" below for details on the format.
* - {@literal font} - The font. See "Font Strings" below for details on the format.
* - {@literal border} - The border. See "Border Strings" below for details on the format.
* - {@literal bgType} - The background type. See "Background Type Values" below for details on the available options.
*
*
* Padding and Margin Strings
* The {@literal padding} and {@literal margin} keys can take values the same format as is used for CSS {@literal margin} and {@literal padding} directives. That is the value
* can be expressed as a space-separated sequence of scalar values (i.e. float values with a unit suffix). Some examples:
*
* - {@literal padding:0px} - Sets all padding to zero pixels.
* - {@literal padding:2mm 1px} - Sets vertical padding to 2 millimetres, and horizontal padding to 1 pixel.
* - {@literal padding:2mm 1px inherit} - Sets top padding to 1 millimetres, horizontal padding to 1 pixel, and bottom padding to inherit the parent style's bottom padding.
* - {@literal padding:1mm 2px inherit 4mm} - Top padding=1 millimetre. Right padding=2 pixels. Bottom padding inherits parent style's bottom padding. Left padding=4 millimetres.
*
*
* All of the examples above use {@literal padding}, but the same format is used for {@literal margin}. They demonstrate the use of 1, 2, 3, and 4 value sequences, and their meaning. In general terms
* these formats can be described as:
*
* - {@literal
} - Sets padding on all sites to {@literal }
* - {@literal
} - Top and bottom padding set to {@literal }. Left and right padding set to {@literal }
* - {@literal
} - Top={@literal }. Left and right = {@literal }. Bottom = {@literal bottom}.
* - {@literal
} - Top={@literal }. Right={@literal right}. Bottom={@literal bottom}. Left={@literal left}. In other words, values applied clock-wise, starting on top side.
*
*
* Font Strings
* Fonts strings can take any of the following formats:
*
* - {@literal
} - E.g. {@literal 3mm Arial.ttf /Arial.ttf} or {@literal 12px native:MainRegular native:MainRegular}
* - {@literal
} - E.g. {@literal 3mm Arial.ttf} or {@literal 3mm native:ItalicBlack}.
* - {@literal
} - E.g. {@literal 3mm} or {@literal 12px}. When only specifying the size, the font family will be dictated by the parent style.
* - {@literal
} - E.g. {@literal Arial.ttf /Arial.ttf} or {@literal native:MainBold native:MainBold}. When omitting font size (as this format does), the size
* is dictated by the parent style.
* - {@literal
|} - E.g. {@literal Arial.ttf} or {@literal /Arial.ttf}, or {@literal native:MainRegular} Strings starting with a {@literal /} are assumed to be files. The corresponding font name
* is then derived by removing the slash and the trailing {@literal .ttf}. When omitting font size (as this format does), the size is dictated by the parent style.
*
*
* Border Strings
* The {@literal border} property accepts several different formats for its value. This is due to the many different kinds of
* borders that can be created. The following are some of the formats.
*
* Line Border
*
* {@literal solid } - E.g. {@literal 1mm solid ff0000}. {@literal } should be expressed as a scalar value with unit. E.g. {@literal 1mm}, or {@literal 2px}.
* {@literal } should be an RGB hex string. E.g {@literal ff0000} for red.
*
* Dashed Border
*
* {@literal dashed } - E.g. {@literal 1mm dashed ff0000}. {@literal } should be expressed as a scalar value with unit. E.g. {@literal 1mm}, or {@literal 2px}.
* {@literal } should be an RGB hex string. E.g {@literal ff0000} for red.
*
* Dotted Border
*
* {@literal dotted } - E.g. {@literal 1mm dotted ff0000}. {@literal } should be expressed as a scalar value with unit. E.g. {@literal 1mm}, or {@literal 2px}.
* {@literal } should be an RGB hex string. E.g {@literal ff0000} for red.
*
* Underline Border
*
* {@literal underline } - E.g. {@literal 1mm underline ff0000}. {@literal } should be expressed as a scalar value with unit. E.g. {@literal 1mm}, or {@literal 2px}.
* {@literal } should be an RGB hex string. E.g {@literal ff0000} for red.
*
* Image Border
*
* {@literal image ... } - A 9-piece image border. The {@literal } .. {@literal } values are strings which refer to images either on the classpath, or in the theme resource file.
* If the image string starts with {@literal /}, then it is assumed to be on the classpath. The order of the images corresponds to the parameters of {@link Border#createImageBorder(com.codename1.ui.Image, com.codename1.ui.Image, com.codename1.ui.Image, com.codename1.ui.Image, com.codename1.ui.Image, com.codename1.ui.Image, com.codename1.ui.Image, com.codename1.ui.Image, com.codename1.ui.Image) }.
*
* {@literal image } - A 9-piece image border, but with the images corresponding to the parameters of {@link Border#createImageBorder(com.codename1.ui.Image, com.codename1.ui.Image, com.codename1.ui.Image) }.
* {@literal horizontalImage } - A 3-piece horizontal image border. Image parameters correspond with {@link Border#createHorizonalImageBorder(com.codename1.ui.Image, com.codename1.ui.Image, com.codename1.ui.Image) } parameters.
* {@literal verticalImage } - A 3-piece horizontal image border. Image parameters correspond with {@link Border#createVerticalImageBorder(com.codename1.ui.Image, com.codename1.ui.Image, com.codename1.ui.Image) } parameters.
* {@literal splicedImage } - A 9-piece image border that is generated from a single image, but with inset values specifying where the image should be sliced to create the 9 sub-images.
*
* Parameters:
*
*
* - {@literal
} The image to use. If this begins with {@literal /}, then the image will be found on the classpath. Otherwise it will be found in the theme resource file.
* - {@literal
}, {@literal }, {@literal }, {@literal } - The insets along which {@literal } is sliced to generate the 9-subimages. These values are
* expressed as a floating point number between 0.0 and 1.0, where 1.0 is the full width or height of the image depending on the orientation (horizontal or vertical) or the inset. If {@literal image}
* is 100 pixels by 100 pixels, then a top inset of 0.4 would cause a slice to occur at 40 pixels from the top of the image (i.e. the top-left, top, and top-right slices would each be 40 pixels high.
*
*
* @author shannah
*/
public class StyleParser {
public static final byte UNIT_INHERIT=99;
/**
* Encapsulates a scalar value with a unit.
*/
public static class ScalarValue {
private byte unit;
private double value;
/**
* Creates a new scalar value given magnitude and unit.
* @param value The value to set.
* @param unit The unit of the value. One of {@link #UNIT_INHERIT}, {@link Style#UNIT_TYPE_DIPS}, {@link Style#UNIT_TYPE_PIXELS}, or {@link Style#UNIT_TYPE_SCREEN_PERCENTAGE}.
*/
public ScalarValue(double value, byte unit) {
this.value = value;
this.unit = unit;
}
public ScalarValue() {
}
/**
* @return the unit of the value. One of {@link #UNIT_INHERIT}, {@link Style#UNIT_TYPE_DIPS}, {@link Style#UNIT_TYPE_PIXELS}, or {@link Style#UNIT_TYPE_SCREEN_PERCENTAGE}.
*/
public byte getUnit() {
return unit;
}
/**
* @param unit the unit of the value. One of {@link #UNIT_INHERIT}, {@link Style#UNIT_TYPE_DIPS}, {@link Style#UNIT_TYPE_PIXELS}, or {@link Style#UNIT_TYPE_SCREEN_PERCENTAGE}.
*/
public void setUnit(byte unit) {
this.unit = unit;
}
/**
* @return the value of the scalar.
*/
public double getValue() {
return value;
}
/**
* @param value the value of the scalar.
*/
public void setValue(double value) {
this.value = value;
}
/**
* Returns the scalar value in CN1 style string format. E.g. 12mm, 3px, 5%, or inherit
* @return
*/
@Override
public String toString() {
switch (unit) {
case UNIT_INHERIT:
return "inherit";
case Style.UNIT_TYPE_DIPS:
return value + "mm";
case Style.UNIT_TYPE_SCREEN_PERCENTAGE:
return value + "%";
default:
return ((int)Math.round(value))+"px";
}
}
/**
* Formats this scalar value (including units) but rounding to the given number
* of decimal places.
* @param decimalPlaces
* @return
*/
public String toString(int decimalPlaces) {
switch (unit) {
case UNIT_INHERIT:
return "inherit";
case Style.UNIT_TYPE_DIPS:
return StyleParser.format(value, decimalPlaces) + "mm";
case Style.UNIT_TYPE_SCREEN_PERCENTAGE:
return StyleParser.format(value, decimalPlaces) + "%";
default:
return ((int)Math.round(value))+"px";
}
}
/**
* Gets the magnitude of this scalar value in pixels.
* @return
*/
public int getPixelValue() {
switch (unit) {
case UNIT_INHERIT:
return 0;
case Style.UNIT_TYPE_DIPS:
return Display.getInstance().convertToPixels((float)value);
case Style.UNIT_TYPE_SCREEN_PERCENTAGE:
return (int)(Display.getInstance().getDisplayWidth() * value / 100f);
default:
return ((int)Math.round(value));
}
}
}
/**
* Encapculates a style string in structured format.
*/
public static class StyleInfo {
Map values;
/**
* Parses the given style strings and encapsulates their details in
* a StyleInfo object.
* @param styleString One or more style strings.
*/
public StyleInfo(String... styleString) {
if (styleString == null) {
this.values = new HashMap();
} else {
HashMap vals = new HashMap();
for (String str : styleString) {
if (str != null && str.length() > 0) {
parseString(vals, str);
}
}
this.values = vals;
}
}
/**
* Creates a new StyleInfo given the parsed Map of keys and values.
* @param values
*/
public StyleInfo(Map values) {
this.values = values;
}
/**
* Creates a new StyleInfo.
*/
public StyleInfo() {
this(new HashMap());
}
/**
* Creates a new style info by copying styles from existing style info.
* @param info Style to copy.
*/
public StyleInfo(StyleInfo info) {
this(info.toStyleString());
}
/**
*
* @return The padding of the style. Will return {@literal null} if padding wasn't specified.
*/
public PaddingInfo getPadding() {
if (values.containsKey("padding")) {
return parsePadding(values.get("padding"));
}
return null;
}
/**
*
* @return The margin of the style. Will return {@literal null} if margin wasn't specified.
*/
public MarginInfo getMargin() {
if (values.containsKey("margin")) {
return parseMargin(values.get("margin"));
}
return null;
}
/**
*
* @return The border of the style. Will return {@literal null} if border wasn't specified.
*/
public BorderInfo getBorder() {
if (values.containsKey("border")) {
return parseBorder(new BorderInfo(), values.get("border"));
}
return null;
}
/**
*
* @return The font of the style. Will return {@literal null} if font wasn't specified.
*/
public FontInfo getFont() {
if (values.containsKey("font")) {
return parseFont(new FontInfo(), values.get("font"));
}
return null;
}
/**
* Sets the font in the style info.
* @param font A valid font style string. E.g. "2mm native:MainRegular"
* @return Self for chaining.
*/
public StyleInfo setFont(String font) {
values.put("font", font);
return this;
}
/**
* Sets the font size in the style.
* @param fontSize A valid font size. E.g. "2mm"
* @return Self for chaining.
*/
public StyleInfo setFontSize(String fontSize) {
FontInfo finfo = getFont();
if (finfo == null) {
finfo = parseFont(new FontInfo(), fontSize);
if (finfo != null) {
setFont(finfo.toString());
}
} else {
FontInfo tmp = parseFont(new FontInfo(), fontSize);
if (tmp != null) {
finfo.setSize(tmp.getSize());
finfo.setSizeUnit(tmp.getSizeUnit());
setFont(finfo.toString());
} else {
finfo.setSizeUnit(UNIT_INHERIT);
setFont(finfo.toString());
}
}
return this;
}
/**
* Sets the font name.
* @param fontName A valid font name. E.g. "native:MainRegular"
* @return Self for chaining.
*/
public StyleInfo setFontName(String fontName) {
FontInfo finfo = getFont();
if (finfo == null) {
finfo = parseFont(new FontInfo(), fontName);
if (finfo != null) {
setFont(finfo.toString());
}
} else {
FontInfo tmp = parseFont(new FontInfo(), fontName);
if (tmp != null) {
finfo.setName(tmp.getName());
finfo.setFile(tmp.getFile());
setFont(finfo.toString());
} else {
finfo.setName(null);
finfo.setFile(null);
setFont(finfo.toString());
}
}
return this;
}
/**
*
* @return The background color of the style. Will return {@literal null} if bgColor wasn't specified.
*/
public Integer getBgColor() {
if (values.containsKey("bgColor")) {
return Integer.parseInt(values.get("bgColor"), 16);
}
return null;
}
/**
*
* @return The foreground color of the style. Will return {@literal null} if fgColor wasn't specified.
*/
public Integer getFgColor() {
if (values.containsKey("fgColor")) {
return Integer.parseInt(values.get("fgColor"), 16);
}
return null;
}
/**
* Sets the foreground color.
* @param fgColor A valid color string. E.g. "ff0000"
* @return Self for chaining.
*/
public StyleInfo setFgColor(String fgColor) {
if (fgColor == null || fgColor.trim().length() == 0) {
values.remove("fgColor");
} else {
values.put("fgColor", fgColor);
}
return this;
}
/**
* Sets the background color.
* @param bgColor A valid color string. E.g. "ff0000"
* @return
*/
public StyleInfo setBgColor(String bgColor) {
if (bgColor == null || bgColor.trim().length() == 0) {
values.remove("bgColor");
} else {
values.put("bgColor", bgColor);
}
return this;
}
/**
* Sets the transparency.
* @param transparency A valid transparency string (0-255). E.g. "255"
* @return Self for chaining.
*/
public StyleInfo setTransparency(String transparency) {
if (transparency == null || transparency.trim().length() == 0) {
values.remove("transparency");
} else {
values.put("transparency", transparency);
}
return this;
}
/**
* Sets the opacity.
* @param opacity A valid opacity string (0-255). E.g. "255"
* @return Self for chaining.
*/
public StyleInfo setOpacity(String opacity) {
if (opacity == null || opacity.trim().length() == 0) {
values.remove("opacity");
} else {
values.put("opacity", opacity);
}
return this;
}
/**
* Sets the padding.
* @param padding A valid padding string. E.g. "1mm", or "2mm 3px 0 5mm"
* @return Self for chaining.
*/
public StyleInfo setPadding(String padding) {
if (padding == null || padding.trim().length() == 0) {
values.remove("padding");
} else {
values.put("padding", padding);
}
return this;
}
/**
* Sets the margin.
* @param margin A valid margin string. E.g. "1mm", or "2mm 3px 0 0"
* @return Self for chaining.
*/
public StyleInfo setMargin(String margin) {
if (margin == null || margin.trim().length() == 0) {
values.remove("margin");
} else {
values.put("margin", margin);
}
return this;
}
/**
* Sets the border
* @param border A valid border string. E.g. "1px solid ff0000", or "splicedImage notes.png 0.25 0.25 0.25 0.25"
* @return Self for chaining.
*/
public StyleInfo setBorder(String border) {
if (border == null || border.trim().length() == 0) {
values.remove("border");
} else {
values.put("border", border);
}
return this;
}
/**
*
* @return The background transparency of the style. Will return {@literal null} if transparency wasn't specified.
*/
public Integer getTransparency() {
if (values.containsKey("transparency")) {
return Integer.parseInt(values.get("transparency"));
}
return null;
}
/**
*
* @return The opacity of the style. Will return {@literal null} if the opacity wasn't specified.
*/
public Integer getOpacity() {
if (values.containsKey("opacity")) {
return Integer.parseInt(values.get("opacity"));
}
return null;
}
/**
*
* @return The alignment of the style. One of {@link Component#LEFT}, {@link Component#RIGHT}, {@link Component#CENTER}. Or {@literal null} if
* alignment wasn't specified.
*/
public Integer getAlignment() {
if (values.containsKey("alignment")) {
return parseAlignment(values.get("alignment"));
}
return null;
}
/**
* Sets the alignment for this style. One of {@link Component#LEFT}, {@link Component#RIGHT}, {@link Component#CENTER}
* @param alignment One of {@link Component#LEFT}, {@link Component#RIGHT}, {@link Component#CENTER}
* @return Self for chaining.
*/
public StyleInfo setAlignment(int alignment) {
values.put("alignment", String.valueOf(alignment));
return this;
}
/**
* Sets the alignment for this style as a String. Accepts either a string representation of the integer values for {@link Component#LEFT}, {@link Component#RIGHT}, {@link Component#CENTER}. Or the
* literal strings "center", "left", or "right". Or null to unset this property.
* @param alignment
* @return
*/
public StyleInfo setAlignment(String alignment) {
if (alignment == null || alignment.length() == 0) {
values.remove("alignment");
} else {
values.put("alignment", alignment);
}
return this;
}
/**
* Returns the alignment as a string. "center", "left", "right", or null.
* @return
*/
public String getAlignmentAsString() {
return getAlignmentString(getAlignment());
}
/**
* Gets the background type. Will return {@literal null} if bgType wasn't specified. Returns one of {@literal Style.BACKGROUND_XXX} constants.
* @return
*/
public Integer getBgType() {
if (values.containsKey("bgType")) {
return parseBgType(values.get("bgType"));
}
return null;
}
/**
* Sets the background type as a string.
* @param type A valid background type string. E.g. "image_scaled_fit"
* @return Self for chaining.
*/
public StyleInfo setBgType(String type) {
values.put("bgType", type);
return this;
}
/**
* Sets the background type as one of the Style.BACKGROUND_XXX constants.
* @param i A background type. One of Style.BACKGROUND_XXX constants.
* @return Self for chaining.
*/
public StyleInfo setBgType(Integer i) {
setBgType(flip(bgTypes).get(i));
return this;
}
/**
* Gets the bgType as a string.
* @return
*/
public String getBgTypeAsString() {
Integer bgType = getBgType();
if (bgType == null) {
return null;
}
Map inverseMap = flip(bgTypes());
if (inverseMap.containsKey(bgType)) {
return inverseMap.get(bgType);
}
return null;
}
/**
*
* @return The background image for the style. Will return {@literal null} if bgImage wasn't specified.
*/
public ImageInfo getBgImage() {
if (values.containsKey("bgImage")) {
ImageInfo out = new ImageInfo(values.get("bgImage"));
return out;
}
return null;
}
/**
* Sets the background image.
* @param bgImage A valid image string. E.g. "notes.png" (to refer to the notes.png image in the theme), or "/notes.png" to refer to notes.png in the src directory.
* @return Self for chaining.
*/
public StyleInfo setBgImage(String bgImage) {
values.put("bgImage", bgImage);
return this;
}
/**
*
* @return The text decoration of the style. Will return {@literal null} if textDecoration wasn't specified. Returns one of {@literal Style.TEXT_DECORATION_XXX} constants.
*/
public Integer getTextDecoration() {
if (values.containsKey("textDecoration")) {
String val = values.get("textDecoration");
if ("3d".equalsIgnoreCase(val)) {
return (int)Style.TEXT_DECORATION_3D;
}
if ("3d_lowered".equalsIgnoreCase(val)) {
return (int)Style.TEXT_DECORATION_3D_LOWERED;
}
if ("3D_SHADOW_NORTH".equalsIgnoreCase(val)) {
return (int)Style.TEXT_DECORATION_3D_SHADOW_NORTH;
}
if ("none".equalsIgnoreCase(val)) {
return (int)Style.TEXT_DECORATION_NONE;
}
if ("overline".equalsIgnoreCase(val)) {
return (int)Style.TEXT_DECORATION_OVERLINE;
}
if ("strikethru".equalsIgnoreCase(val)) {
return (int)Style.TEXT_DECORATION_STRIKETHRU;
}
if ("underine".equalsIgnoreCase(val)) {
return (int)Style.TEXT_DECORATION_UNDERLINE;
}
}
return null;
}
/**
* Gets the text decoration as a string.
* @return A valid text decoration string or null. Text decoration strings include "3d", "3d_lowered", "3d_shadow_north", "none", "strikethru", "overline", and "underline"
*/
public String getTextDecorationAsString() {
if (values.containsKey("textDecoration")) {
return values.get("textDecoration");
}
return null;
}
/**
* Builds a style string that is encapsulated by this style object. The output of this can be passed to methods like {@link Component#setInlineAllStyles(java.lang.String) }
* @return A style string.
*/
public String toStyleString() {
StringBuilder sb = new StringBuilder();
FontInfo finfo = getFont();
if (finfo != null) {
sb.append("font:").append(finfo.toString()).append("; ");
}
BorderInfo binfo = getBorder();
if (binfo != null) {
sb.append("border:").append(binfo.toString()).append("; ");
}
Integer bgColor = getBgColor();
if (bgColor != null) {
sb.append("bgColor:").append(Integer.toHexString(bgColor)).append("; ");
}
Integer fgColor = getFgColor();
if (fgColor != null) {
sb.append("fgColor:").append(Integer.toHexString(fgColor)).append("; ");
}
Integer transparency = getTransparency();
if (transparency != null) {
sb.append("transparency:").append(transparency).append("; ");
}
Integer opacity = getOpacity();
if (opacity != null) {
sb.append("opacity:").append(opacity).append("; ");
}
Integer bgType = getBgType();
if (bgType != null) {
sb.append("bgType:").append(getBgTypeAsString()).append("; ");
}
ImageInfo image = getBgImage();
if (image != null) {
sb.append("bgImage:").append(getBgImage().toString()).append("; ");
}
Integer alignment = getAlignment();
if (alignment != null) {
sb.append("alignment:").append(getAlignmentAsString()).append("; ");
}
MarginInfo margin = getMargin();
if (margin != null) {
sb.append("margin:").append(margin.toString()).append("; ");
}
PaddingInfo padding = getPadding();
if (padding != null) {
sb.append("padding:").append(padding.toString()).append("; ");
}
Integer textDecoration = getTextDecoration();
if (textDecoration != null) {
sb.append("textDecoration:").append(getTextDecorationAsString()).append("; ");
}
return sb.toString().trim();
}
}
/**
* Encapsulates an image that is referenced by a style string.
*/
public static class ImageInfo {
private String image;
/**
* Creates an ImageInfo to wrap the specified image.
* @param image Either the path to an image on the classpath (signified by a leading {@literal '/'}, or
* the name of an image that can be found in the theme resource file.
*/
public ImageInfo(String image) {
this.image = image;
}
@Override
public String toString() {
return image;
}
/**
* Gets the image object that this image info references.
* @param theme The theme resource file to use to get the image. Note: If the image name has a leading {@literal '/'}, then
* the image will be loaded from the classpath. Otherwise the theme resource file will be used.
* @return
*/
public Image getImage(Resources theme) {
return StyleParser.getImage(theme, image);
}
}
/**
* Parses a style string into a Map
* @param out
* @param str
* @return
*/
static StyleInfo parseString(StyleInfo out, String str) {
Map map = parseString(new HashMap(), str);
for (Map.Entry e : map.entrySet()) {
out.values.put(e.getKey(), e.getValue());
}
return out;
}
static StyleInfo parseString(String str) {
return parseString(new StyleInfo(), str);
}
static Map parseString(Map out, String str) {
String[] rules = Util.split(str, ";");
for (String rule : rules) {
rule = rule.trim();
if (rule.length() == 0) {
continue;
}
int pos = rule.indexOf(":");
if (pos == -1) {
continue;
}
String key = rule.substring(0, pos);
if ("font".equals(key) && out.containsKey("font")) {
// We may need to merge font rules
FontInfo newFinfo = StyleParser.parseFont(new FontInfo(), rule.substring(pos+1));
FontInfo origFinfo = StyleParser.parseFont(new FontInfo(), out.get("font"));
Float newSize = newFinfo.getSize();
if (newSize != null && newFinfo.getSizeUnit() != StyleParser.UNIT_INHERIT) {
origFinfo.setSize(newSize);
origFinfo.setSizeUnit(newFinfo.getSizeUnit());
}
if (newFinfo.getName() != null) {
origFinfo.setName(newFinfo.getName());
}
if (newFinfo.getFile() != null) {
origFinfo.setFile(newFinfo.getFile());
}
out.put(key, origFinfo.toString());
} else {
out.put(key, rule.substring(pos+1));
}
}
return out;
}
/**
* Base class for style values that consist of 4 scalar values, such as padding and margin.
*/
public static class BoxInfo {
protected ScalarValue[] values;
/**
* Creates a new box with the specified scalar values.
* @param values A 4-element array of scalar values.
*/
public BoxInfo(ScalarValue[] values) {
if (values.length != 4) throw new IllegalArgumentException("BoxInfo expected 4-element array");
this.values = values;
}
/**
* Returns string of values in {@literal } format.
* @return
*/
@Override
public String toString() {
return toString(Component.TOP)+" "+toString(Component.RIGHT)+" "+toString(Component.BOTTOM)+" "+toString(Component.LEFT);
}
/**
* Returns the string representation of one of the sides of the box.
* @param side One of {@link Component#TOP}, {@link Component#RIGHT}, {@link Component#BOTTOM}, {@link Component#LEFT}.
* @return
*/
public String toString(int side) {
return values[side].toString();
}
/**
* Gets the scalar values of this box as a 4-element array.
* @return
*/
public ScalarValue[] getValues() {
return values;
}
/**
* Sets the scalar values of this box as a 4-element array.
* @param values
*/
public void setValues(ScalarValue[] values) {
if (values.length != 4) {
throw new IllegalArgumentException("BoxInfo requires array with 4 values.");
}
this.values = values;
}
/**
* Gets a value for a side.
* @param side One of {@link Component#TOP}, {@link Component#RIGHT}, {@link Component#BOTTOM}, {@link Component#LEFT}.
* @return The value portion of the scalar value.
*/
public ScalarValue getValue(int side) {
return values[side];
}
}
/**
* Encapsulates information about the padding in a style string.
*/
public static class PaddingInfo extends BoxInfo {
/**
* Creates a new PaddingInfo.
* @param values 4-element array of scalar values. Indices are {@link Component#TOP}, {@link Component#BOTTOM}, {@link Component#LEFT} and {@link Component#RIGHT}.
*/
public PaddingInfo(ScalarValue[] values) {
super(values);
}
/**
* Generates a 4-element float array with the padding values - in the same format as used in the theme resource files. This will use the provided base style
* to calculate the padding for sides that were specified as {@literal inherit}.
* @param baseStyle The base style used to get the padding on sides where the style string specified {@literal inherit}.
* @return 4-element float array.
*/
float[] createPadding(Style baseStyle) {
float[] out = new float[4];
for (int i=0; i<4; i++) {
out[i] = createPadding(baseStyle, i);
}
return out;
}
float createPadding(Style baseStyle, int side) {
ScalarValue v = values[side];
switch (v.unit) {
case UNIT_INHERIT: {
int px = baseStyle.getPadding(side);
byte[] units = baseStyle.getPaddingUnit();
if (units == null) {
return px;
}
switch (units[side]) {
case Style.UNIT_TYPE_DIPS:
return px / (float)Display.getInstance().convertToPixels(1f);
default:
return px;
}
}
default:
return (float)v.value;
}
}
byte[] createPaddingUnit(Style baseStyle) {
byte[] out = new byte[4];
for (int i=0; i<4; i++) {
out[i] = createPaddingUnit(baseStyle, i);
}
return out;
}
byte createPaddingUnit(Style baseStyle, int side) {
ScalarValue v = values[side];
switch (v.unit) {
case UNIT_INHERIT: {
byte[] units = baseStyle.getPaddingUnit();
if (units == null) {
return Style.UNIT_TYPE_PIXELS;
}
return units[side];
}
default:
return v.unit;
}
}
}
/**
* Encapsulates information about the padding in a style string.
*/
public static class MarginInfo extends BoxInfo {
/**
* Creates a new MarginInfo.
* @param values 4-element array of scalar values. Indices are {@link Component#TOP}, {@link Component#BOTTOM}, {@link Component#LEFT} and {@link Component#RIGHT}.
*/
public MarginInfo(ScalarValue[] values) {
super(values);
}
float[] createMargin(Style baseStyle) {
float[] out = new float[4];
for (int i=0; i<4; i++) {
out[i] = createMargin(baseStyle, i);
}
return out;
}
float createMargin(Style baseStyle, int side) {
ScalarValue v = values[side];
switch (v.unit) {
case UNIT_INHERIT: {
int px = baseStyle.getPadding(side);
byte[] units = baseStyle.getMarginUnit();
if (units == null) {
return px;
}
switch (units[side]) {
case Style.UNIT_TYPE_DIPS:
return px / (float)Display.getInstance().convertToPixels(1f);
default:
return px;
}
}
default:
return (float)v.value;
}
}
byte[] createMarginUnit(Style baseStyle) {
byte[] out = new byte[4];
for (int i=0; i<4; i++) {
out[i] = createMarginUnit(baseStyle, i);
}
return out;
}
byte createMarginUnit(Style baseStyle, int side) {
ScalarValue v = values[side];
switch (v.unit) {
case UNIT_INHERIT: {
byte[] units = baseStyle.getMarginUnit();
if (units == null) {
return Style.UNIT_TYPE_PIXELS;
}
return units[side];
}
default:
return v.unit;
}
}
}
static MarginInfo parseMargin(MarginInfo out, String margin) {
out.setValues(parseTRBLValue(margin));
return out;
}
static MarginInfo parseMargin(String margin) {
ScalarValue[] v = parseTRBLValue(margin);
if(v == null) {
return null;
}
return new MarginInfo(v);
}
static String parseMargin(Style baseStyle, String margin) {
ScalarValue[] vals = parseTRBLValue(margin);
StringBuilder sb = new StringBuilder();
if (vals[Component.TOP].getUnit() == UNIT_INHERIT) {
sb.append(baseStyle == null ? 0 : baseStyle.margin[Component.TOP]);
} else {
sb.append(vals[Component.TOP].getValue());
}
sb.append(",");
if (vals[Component.BOTTOM].getUnit() == UNIT_INHERIT) {
sb.append(baseStyle == null ? 0 : baseStyle.margin[Component.BOTTOM]);
} else {
sb.append(vals[Component.BOTTOM].getValue());
}
sb.append(",");
if (vals[Component.LEFT].getUnit() == UNIT_INHERIT) {
sb.append(baseStyle == null ? 0 : baseStyle.margin[Component.LEFT]);
} else {
sb.append(vals[Component.LEFT].getValue());
}
sb.append(",");
if (vals[Component.RIGHT].getUnit() == UNIT_INHERIT) {
sb.append(baseStyle == null ? 0 : baseStyle.margin[Component.RIGHT]);
} else {
sb.append(vals[Component.RIGHT].getValue());
}
return sb.toString();
}
static PaddingInfo parsePadding(PaddingInfo out, String padding) {
out.setValues(parseTRBLValue(padding));
return out;
}
static PaddingInfo parsePadding(String padding) {
ScalarValue[] v = parseTRBLValue(padding);
if(v == null) {
return null;
}
return new PaddingInfo(v);
}
static String parsePadding(Style baseStyle, String padding) {
ScalarValue[] vals = parseTRBLValue(padding);
StringBuilder sb = new StringBuilder();
if (vals[Component.TOP].getUnit() == UNIT_INHERIT) {
sb.append(baseStyle == null ? 0 : baseStyle.padding[Component.TOP]);
} else {
sb.append(vals[Component.TOP].getValue());
}
sb.append(",");
if (vals[Component.BOTTOM].getUnit() == UNIT_INHERIT) {
sb.append(baseStyle == null ? 0 : baseStyle.padding[Component.BOTTOM]);
} else {
sb.append(vals[Component.BOTTOM].getValue());
}
sb.append(",");
if (vals[Component.LEFT].getUnit() == UNIT_INHERIT) {
sb.append(baseStyle == null ? 0 : baseStyle.padding[Component.LEFT]);
} else {
sb.append(vals[Component.LEFT].getValue());
}
sb.append(",");
if (vals[Component.RIGHT].getUnit() == UNIT_INHERIT) {
sb.append(baseStyle == null ? 0 : baseStyle.padding[Component.RIGHT]);
} else {
sb.append(vals[Component.RIGHT].getValue());
}
return sb.toString();
}
static byte[] parsePaddingUnit(Style baseStyle, String padding) {
ScalarValue[] vals = parseTRBLValue(padding);
byte[] out = new byte[4];
for (int i=0; i<4; i++) {
if (vals[i].getUnit() == UNIT_INHERIT) {
out[i] = baseStyle == null ? Style.UNIT_TYPE_PIXELS : baseStyle.paddingUnit[i];
} else {
out[i] = vals[i].getUnit();
}
}
return out;
}
static byte[] parseMarginUnit(Style baseStyle, String margin) {
ScalarValue[] vals = parseTRBLValue(margin);
byte[] out = new byte[4];
for (int i=0; i<4; i++) {
if (vals[i].getUnit() == UNIT_INHERIT) {
out[i] = baseStyle == null ? Style.UNIT_TYPE_PIXELS : baseStyle.marginUnit[i];
} else {
out[i] = vals[i].getUnit();
}
}
return out;
}
static Integer parseAlignment(String alignment) {
if (Character.isDigit(alignment.charAt(0))) {
return Integer.parseInt(alignment);
} else if ("center".equalsIgnoreCase(alignment)) {
return Component.CENTER;
} else if ("left".equalsIgnoreCase(alignment)) {
return Component.LEFT;
} else if ("right".equalsIgnoreCase(alignment)) {
return Component.RIGHT;
}
return null;
}
private static String getAlignmentString(Integer alignment) {
if (alignment == null) {
return null;
}
switch (alignment) {
case Component.CENTER: return "center";
case Component.LEFT: return "left";
case Component.RIGHT: return "right";
}
return null;
}
private static int getPixelValue(String val) {
ScalarValue v = parseSingleTRBLValue(val);
switch (v.getUnit()) {
case Style.UNIT_TYPE_PIXELS:
return (int)Math.round(v.getValue());
case Style.UNIT_TYPE_DIPS:
return Display.getInstance().convertToPixels((float)v.getValue());
case Style.UNIT_TYPE_SCREEN_PERCENTAGE :
return (int)Math.round(Display.getInstance().getDisplayWidth() * v.getValue() / 100.0);
}
return 0;
}
private static float getMMValue(String val) {
ScalarValue v = parseSingleTRBLValue(val);
switch (v.getUnit()) {
case Style.UNIT_TYPE_PIXELS:
return (float)v.getValue() / Display.getInstance().convertToPixels(1f);
case Style.UNIT_TYPE_DIPS:
return (float)v.getValue();
case Style.UNIT_TYPE_SCREEN_PERCENTAGE :
return (int)Math.round(Display.getInstance().getDisplayWidth() * v.getValue() / 100.0) / Display.getInstance().convertToPixels(1f);
}
return 0;
}
private static String parseStroke(BorderInfo out, String rem) {
// parse the stroke
int p1 = rem.indexOf("(");
int p2 = rem.indexOf(")");
if (p1 != -1 && p2 != -1) {
String strokeStr = rem.substring(p1+1, p2).trim();
String[] strokeArgs = Util.split(strokeStr, " ");
for (String strokeArg : strokeArgs) {
strokeArg = strokeArg.trim();
if (strokeArg.endsWith("mm") || strokeArg.endsWith("px")) {
ScalarValue sv = parseScalarValue(strokeArg);
out.width = (float)sv.value;
out.widthUnit = sv.unit;
} else if (strokeArg.length() > 0) {
//int strokeColor = Integer.parseInt(strokeArg, 16);
if (strokeArg.length() == 8) {
// there is an alpha bit
out.setStrokeOpacity(Integer.parseInt(strokeArg.substring(0, 2), 16) & 0xff);
out.setStrokeColor(Integer.parseInt(strokeArg.substring(2), 16) & 0xffffff);
} else {
out.setStrokeOpacity(0xff);
out.setStrokeColor(Integer.parseInt(strokeArg, 16) & 0xffffff);
}
}
}
rem = rem.substring(p2+1);
} else {
rem = rem.substring(6); // at least get past stroke
}
return rem;
}
private static String parseShadow(BorderInfo out, String rem) {
int p1 = rem.indexOf("(");
int p2 = rem.indexOf(")");
if (p1 != -1 && p2 != -1) {
String shadowStr = rem.substring(p1+1, p2);
String[] shadowArgs = Util.split(shadowStr, " ");
for (String shadowArg : shadowArgs) {
shadowArg = shadowArg.trim();
if (shadowArg.startsWith("shadowSpread:") || shadowArg.endsWith("mm") || shadowArg.endsWith("px")) {
int colonPos = shadowArg.indexOf(":");
if (colonPos != -1) {
shadowArg = shadowArg.substring(colonPos+1);
}
out.setShadowSpread(parseScalarValue(shadowArg));
} else if (shadowArg.startsWith("x:")) {
out.setShadowX((Float) Float.parseFloat(shadowArg.substring(shadowArg.indexOf(":")+1).trim()));
} else if (shadowArg.startsWith("y:")) {
out.setShadowY((Float) Float.parseFloat(shadowArg.substring(shadowArg.indexOf(":")+1).trim()));
} else if (shadowArg.startsWith("blur:")) {
out.setShadowBlur((Float) Float.parseFloat(shadowArg.substring(shadowArg.indexOf(":")+1).trim()));
} else if (shadowArg.startsWith("opacity:")) {
out.setShadowOpacity((Integer) Integer.parseInt(shadowArg.substring(shadowArg.indexOf(":")+1).trim()));
}
}
if (out.getShadowSpread() == null) {
out.setShadowSpread(parseScalarValue("0.5mm"));
}
if (out.getShadowX() == null) {
out.setShadowX((Float) (float)0.5);
}
if (out.getShadowY() == null) {
out.setShadowY((Float) (float)0.5);
}
if (out.getShadowBlur() == null) {
out.setShadowBlur((Float) (float)0.1);
}
if (out.getShadowOpacity() == null) {
out.setShadowOpacity((Integer) 128);
}
rem = rem.substring(p2+1);
} else {
rem = rem.substring(6); // at least get past the shadow param
}
return rem;
}
private static BorderInfo parseRoundBorder(BorderInfo out, String args, String[] parts1) {
int plen = parts1.length;
out.setType("round");
if (plen > 1) {
int nextSpacePos;
String rem = args;
while ((nextSpacePos = rem.indexOf(" ")) != -1) {
rem = rem.substring(nextSpacePos+1).trim();
if (rem.startsWith("rect")) {
out.setRectangle((Boolean) true);
} else if (rem.startsWith("stroke")) {
rem = parseStroke(out, rem);
} else if (rem.startsWith("shadow")) {
rem = parseShadow(out, rem);
} else if (rem.length() == 8 || rem.indexOf(" ") == 8) {
String colorStr = rem.substring(0, 8);
out.color = Integer.parseInt(colorStr.substring(2), 16) & 0xffffff;
out.setOpacity((Integer) Integer.parseInt(colorStr.substring(0, 2), 16) & 0xff);
rem = rem.substring(8);
} else {
int spacePos = rem.indexOf(" ");
String colorStr = rem;
if (spacePos != -1) {
colorStr = colorStr.substring(0, spacePos);
}
if (colorStr.length() > 0 && colorStr.length() <= 6) {
out.color = Integer.parseInt(colorStr, 16) & 0xffffff;
out.setOpacity((Integer) 255);
}
rem = rem.substring(colorStr.length());
}
}
}
return out;
}
private static BorderInfo parseRoundRectBorder(BorderInfo out, String args, String[] parts1) {
int plen = parts1.length;
out.setType("roundRect");
if (plen > 1) {
int nextSpacePos;
String rem = args;
while ((nextSpacePos = rem.indexOf(" ")) != -1) {
rem = rem.substring(nextSpacePos+1).trim();
if (rem.startsWith("stroke")) {
rem = parseStroke(out, rem);
} else if (rem.startsWith("shadow")) {
rem = parseShadow(out, rem);
} else if (rem.startsWith("-") || rem.startsWith("+")) {
boolean value = rem.charAt(0) == '+';
String flagName = rem.substring(1);
if (flagName.startsWith("top-only")) {
out.setTopOnlyMode((Boolean) value);
} else if (flagName.startsWith("bottom-only")) {
out.setBottomOnlyMode((Boolean) value);
} else if (flagName.startsWith("top-left")) {
out.setTopLeftMode((Boolean) value);
} else if (flagName.startsWith("top-right")) {
out.setTopRightMode((Boolean) value);
} else if (flagName.startsWith("bottom-left")) {
out.setBottomLeftMode((Boolean) value);
} else if (flagName.startsWith("bottom-right")) {
out.setBottomRightMode((Boolean) value);
}
} else if (rem.length()> 0 && Character.isDigit(rem.charAt(0))) {
out.setCornerRadius(getMMValue(rem));
}
}
}
return out;
}
static BorderInfo parseBorder(BorderInfo out, String args) {
if (args == null) {
out.setType("empty");
return out;
}
args = args.trim();
if ("none".equals(args)) {
out.setType("empty");
return out;
}
String[] parts1 = Util.split(args, " ");
int plen = parts1.length;
if (plen == 0) {
out.setType("empty");
return out;
}
if (plen > 3 && ("image".equals(parts1[0]) || "horizontalImage".equals(parts1[0]) || "verticalImage".equals(parts1[0]))) {
out.setType(parts1[0]);
out.setImages(new String[plen-1]);
for (int i=1; i decimalPlaces) {
dStr = dStr.substring(0, dStr.length() - (decLen - decimalPlaces));
d = Double.parseDouble(dStr);
}
}
return d;
}
/**
* For a line/dashed/dotted/underline/round border, the thickness value.
* @return the width
* @see #getWidthUnit()
*/
public Float getWidth() {
return width;
}
/**
* Gets the border thickness as a scalar value. This is effectively the same
* value as returned by {@link #getWidth() } and {@link #getWidthUnit() }
* @return The thickness of the border. Used with line, dashed, dotted, underline, and round borders.
*/
public ScalarValue getThickness() {
return new ScalarValue(width, widthUnit);
}
/**
* For line/dashed/dotted/underline border. The thickness in pixels.
* @return The thickness in pixels of the border line.
* @asee #getWidth()
*/
public Integer getWidthInPixels() {
if (width == null) {
return null;
}
if (widthUnit == Style.UNIT_TYPE_DIPS) {
return Display.getInstance().convertToPixels(width);
} else {
return width.intValue();
}
}
/**
* For a line/dashed/dotted/underline/round border, gets the thickness value.
* @param width the width to set
* @see #setWidthUnit(byte)
*/
public void setWidth(Float width) {
this.width = width;
}
/**
* For a line/dashed/dotted/underline/round border, gets the unit of the thickness value.
* @return the widthUnit
* @see #getWidth()
*/
public byte getWidthUnit() {
return widthUnit;
}
/**
* For a line/dashed/dotted/underline/round border, sets the unit of the thickness value.
*
* @param widthUnit the widthUnit to set
* @see #setWidth(java.lang.Float)
*/
public void setWidthUnit(byte widthUnit) {
this.widthUnit = widthUnit;
}
/**
* For a line/dashed/dotted/underline/round border, sets the color. For round border
* this gets the fill color. For line border variants, it gets the stroke color.
* @return the color
*
*/
public Integer getColor() {
return color;
}
/**
* For a line/dashed/dotted/underline/round border, gets the color. For
* round border, this sets the fill color. For line border variants, it sets the stroke color.
* @param color the color to set
*/
public void setColor(Integer color) {
this.color = color;
}
/**
* Gets the fill opacity of round border. Only used for round border
* @return the opacity The opacity of the round border.
*/
public Integer getOpacity() {
return opacity;
}
/**
* Sets teh fill opacity of round border. Only used for round border.
* @param opacity the opacity to set
*/
public void setOpacity(Integer opacity) {
this.opacity = opacity;
}
/**
* Gets the stroke color for round border. This is only used for round border.
* Line border variants should use {@link #getColor() }
* @return the strokeColor
*/
public Integer getStrokeColor() {
return strokeColor;
}
/**
* Sets the stroke color for round border. This is only used for round border.
* Line border variants should use {@link #setColor(java.lang.Integer) }
* @param strokeColor the strokeColor to set
*/
public void setStrokeColor(Integer strokeColor) {
this.strokeColor = strokeColor;
}
/**
* Gets the stroke opacity for round border. This is only used for round border.
* @return the strokeOpacity
*/
public Integer getStrokeOpacity() {
return strokeOpacity;
}
/**
* Sets the stroke opacity for round border. This is only used for round border.
* @param strokeOpacity the strokeOpacity to set
*/
public void setStrokeOpacity(Integer strokeOpacity) {
this.strokeOpacity = strokeOpacity;
}
/**
* Sets the shadow opacity for round border. This is only used for round border.
* @return the shadowOpacity
*/
public Integer getShadowOpacity() {
return shadowOpacity;
}
/**
* Sets the shadow opacity for round border. This is only used for round border.
* @param shadowOpacity the shadowOpacity to set
*/
public void setShadowOpacity(Integer shadowOpacity) {
this.shadowOpacity = shadowOpacity;
}
/**
* Gets the shadowX property of round border.
* @return the shadowX
*/
public Float getShadowX() {
return shadowX;
}
/**
* Sets the shadowX property of round border.
* @param shadowX the shadowX to set
*/
public void setShadowX(Float shadowX) {
this.shadowX = shadowX;
}
/**
* Gets the shadowY property of round border.
* @return the shadowY
*/
public Float getShadowY() {
return shadowY;
}
/**
* Sets the shadowY property of round border.
* @param shadowY the shadowY to set
*/
public void setShadowY(Float shadowY) {
this.shadowY = shadowY;
}
/**
* Gets the blur for round border.
* @return the shadowBlur
*/
public Float getShadowBlur() {
return shadowBlur;
}
/**
* Sets the blur for round border.
* @param shadowBlur the shadowBlur to set
*/
public void setShadowBlur(Float shadowBlur) {
this.shadowBlur = shadowBlur;
}
/**
* Gets the shadow spread for round border.
* @return the shadowSpread
*/
public ScalarValue getShadowSpread() {
return shadowSpread;
}
/**
* Sets the shadow spread for round border.
* @param shadowSpread the shadowSpread to set
*/
public void setShadowSpread(ScalarValue shadowSpread) {
this.shadowSpread = shadowSpread;
}
/**
* Sets the shadow spread for round border as a string. String must be valid scalar
* value. E.g. 2mm, or 3px.
* @param val
*/
public void setShadowSpread(String val) {
this.shadowSpread = parseScalarValue(val);
}
/**
* Checks whether round border should grow to a rectangle. Only used for round border.
* @return the rectangle
*/
public Boolean getRectangle() {
return rectangle;
}
/**
* Sets whether round border should grow to a rectangle. Only used for round border.
* @param rectangle the rectangle to set
*/
public void setRectangle(Boolean rectangle) {
this.rectangle = rectangle;
}
/**
* Used only for roundRect border. The value to set for {@link RoundRectBorder#topOnlyMode(boolean) }. Returns {@literal null}
* if this flag isn't set at all.
* @return the topOnlyMode
*/
public Boolean getTopOnlyMode() {
return topOnlyMode;
}
/**
*
* Used only for roundRect border. The value to set for {@link RoundRectBorder#topOnlyMode(boolean) }. Set to {@literal null} to
* not set this flag at all.
*
* @param topOnlyMode the topOnlyMode to set
*/
public void setTopOnlyMode(Boolean topOnlyMode) {
this.topOnlyMode = topOnlyMode;
}
/**
* Used for roundRect border. The value to set for {@link RoundRectBorder#bottomOnlyMode(boolean) }. Returns {@literal null} if
* this property is ignored.
* @return the bottomOnlyMode
*/
public Boolean getBottomOnlyMode() {
return bottomOnlyMode;
}
/**
* Used for roundRect border. The value to set for {@link RoundRectBorder#bottomOnlyMode(boolean) }. Set to {@literal null} to
* ignore this property.
* @param bottomOnlyMode the bottomOnlyMode to set
*/
public void setBottomOnlyMode(Boolean bottomOnlyMode) {
this.bottomOnlyMode = bottomOnlyMode;
}
/**
* Used for roundRect border. The value to set for {@link RoundRectBorder#topLeftMode(boolean) }. Returns {@literal null} if
* this property is ignored.
* @return the topLeftMode
*/
public Boolean getTopLeftMode() {
return topLeftMode;
}
/**
* Used for roundRect border. The value to set for {@link RoundRectBorder#topLeftMode(boolean) }. Set {@literal null} to ignore
* property.
* @param topLeftMode the topLeftMode to set
*/
public void setTopLeftMode(Boolean topLeftMode) {
this.topLeftMode = topLeftMode;
}
/**
* Used for roundRect border. The value to set for {@link RoundRectBorder#topRightMode(boolean)}. Returns {@literal null} if
* property is ignored.
* @return the topRightMode
*/
public Boolean getTopRightMode() {
return topRightMode;
}
/**
* Used for roundRect border. The value to set for {@link RoundRectBorder#topRightMode(boolean) }. Sets {@literal null} to ignore
* property.
* @param topRightMode the topRightMode to set
*/
public void setTopRightMode(Boolean topRightMode) {
this.topRightMode = topRightMode;
}
/**
* Used for roundRect border. The value to set for {@link RoundRectBorder#bottomLeftMode(boolean) }. Returns {@literal null} if
* property is ignored.
* @return the bottomLeftMode
*/
public Boolean getBottomLeftMode() {
return bottomLeftMode;
}
/**
* Used for roundRect border. The value to set for {@link RoundRectBorder#bottomLeftMode(boolean) }. Set {@literal null} to ignore
* property.
* @param bottomLeftMode the bottomLeftMode to set
*/
public void setBottomLeftMode(Boolean bottomLeftMode) {
this.bottomLeftMode = bottomLeftMode;
}
/**
* Used for roundRect border. The value to set for {@link RoundRectBorder#bottomRightMode(boolean) }. Returns {@literal null}
* if property is ignored.
* @return the bottomRightMode
*/
public Boolean getBottomRightMode() {
return bottomRightMode;
}
/**
* Used for roundRect border. The value to set for {@link RoundRectBorder#bottomRightMode(boolean) }. Set {@literal null} to ignore
* property.
* @param bottomRightMode the bottomRightMode to set
*/
public void setBottomRightMode(Boolean bottomRightMode) {
this.bottomRightMode = bottomRightMode;
}
/**
* Used for roundRect border. The corner radius.
* @return the cornerRadius
* @see RoundRectBorder#cornerRadius(float)
*/
public Float getCornerRadius() {
return cornerRadius;
}
/**
* Used for roundRect border. Sets the corner radius.
* @param cornerRadius the cornerRadius to set
* @see RoundRectBorder#cornerRadius(float)
*/
public void setCornerRadius(Float cornerRadius) {
this.cornerRadius = cornerRadius;
}
/**
* Used for roundRect border. Sets the corner radius as a scalar value. E.g. "2mm" or "2px'
* @param cornerRadius the cornerRadius to set
* @see RoundRectBorder#cornerRadius(float)
* @see #setCornerRadius(java.lang.Float)
* @see #setCornerRadius(com.codename1.ui.plaf.StyleParser.ScalarValue)
* @see RoundRectBorder#cornerRadius(float)
*/
public void setCornerRadius(String cornerRadius) {
setCornerRadius(getMMValue(cornerRadius));
}
/**
* Used for roundRect border. Sets the corner radius
* @param sv The corner radius to set.
* @see #setCornerRadius(java.lang.Float)
* @see #setCornerRadius(java.lang.String)
* @see RoundRectBorder#cornerRadius(float)
*/
public void setCornerRadius(ScalarValue sv) {
switch (sv.getUnit()) {
case Style.UNIT_TYPE_DIPS:
setCornerRadius((float)sv.getValue());
break;
default:
setCornerRadius(sv.getPixelValue() / (float)Display.getInstance().convertToPixels(1f));
}
}
}
/**
* Encapsulates the value of the {@literal font} property in a style string.
*/
public static class FontInfo {
private Float size;
private byte sizeUnit;
private String name;
private String file;
/**
* Returns the font in a format that can be used as the value of the {@literal font} property of a style string.
* @return
*/
@Override
public String toString() {
return (sizeString("")+nameString(" ")+fileString(" ")).trim();
}
/**
* Gets the font size as a style string. E.g. 18mm, or 12px. If unit is inherit, this will just return an empty string.
* @param prefix String to prefix to the size.
* @return
*/
public String sizeString(String prefix) {
if (getSize() == null) return prefix;
if (getSizeUnit() == Style.UNIT_TYPE_DIPS) {
return prefix + getSize() + unitString();
} else if (getSizeUnit() == StyleParser.UNIT_INHERIT) {
return prefix;
} else {
return prefix + getSize().intValue() + unitString();
}
}
/**
* Gets the size of the font in pixels.
* @param baseStyle The base style to use in case the font size isn't specified in the style string.
* @return
*/
public float getSizeInPixels(Style baseStyle) {
if (getSize() == null || getSizeUnit() == UNIT_INHERIT) {
Font f = baseStyle.getFont();
if (f == null) f = Font.getDefaultFont();
float pixS = f.getPixelSize();
if (pixS < 1) {
pixS = f.getHeight();
}
return pixS;
} else {
switch (getSizeUnit()) {
case Style.UNIT_TYPE_DIPS:
return Display.getInstance().convertToPixels(getSize());
default:
return getSize();
}
}
}
private String unitString() {
switch (getSizeUnit()) {
case Style.UNIT_TYPE_DIPS: return "mm";
case Style.UNIT_TYPE_PIXELS: return "px";
}
return "px";
}
private String nameString(String prefix) {
if (getName() == null) return prefix;
return prefix + getName();
}
private String fileString(String prefix) {
if (getFile() == null) return prefix;
return prefix + getFile();
}
/**
* Gets the font size.
* @return the size
* @see #getSizeUnit()
*/
public Float getSize() {
return size;
}
/**
* Sets the font size.
* @param size the size to set
* @see #setSizeUnit(byte)
*/
public void setSize(Float size) {
this.size = size;
}
/**
* Gets the font size unit. One of {@link Style#UNIT_TYPE_DIPS}, {@link Style#UNIT_TYPE_PIXELS}, or {@link #UNIT_INHERIT}.
* @return the sizeUnit
*/
public byte getSizeUnit() {
return sizeUnit;
}
/**
* Sets the font size unit. One of {@link Style#UNIT_TYPE_DIPS}, {@link Style#UNIT_TYPE_PIXELS}, or {@link #UNIT_INHERIT}.
* @param sizeUnit the sizeUnit to set
*/
public void setSizeUnit(byte sizeUnit) {
this.sizeUnit = sizeUnit;
}
/**
* Gets the name of the font.
* @return the name
*/
public String getName() {
return name;
}
/**
* Sets the name of the font.
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* Gets the font file name. Should start with "/".
* @return the file
*/
public String getFile() {
return file;
}
/**
* Sets the font file name. Should start with "/".
* @param file the file to set
*/
public void setFile(String file) {
this.file = file;
}
/**
* Creates a font based on this font information.
* @param baseStyle The base style to use in cases where aspects of the font aren't explicitly specified in the style string.
* @return A font.
*/
public Font createFont(Style baseStyle) {
Font f;
if(name == null) {
if(baseStyle == null) {
f = Font.getDefaultFont();
} else {
f = baseStyle.getFont();
}
} else {
f = Font.createTrueTypeFont(name, file);
}
if (f == null || (getSize() != null && !f.isTTFNativeFont())) {
f = Font.createTrueTypeFont(Font.NATIVE_MAIN_REGULAR, Font.NATIVE_MAIN_REGULAR);
}
if (f != null) {
if (f.isTTFNativeFont()) {
return f.derive(getSizeInPixels(baseStyle), f.getStyle());
} else {
return Font.getDefaultFont();
}
} else {
return Font.getDefaultFont();
}
}
}
private static FontInfo parseFontSize(FontInfo out, String arg) {
arg = arg.trim();
ScalarValue sizeVal = parseSingleTRBLValue(arg);
out.setSize((Float) (float)sizeVal.getValue());
out.setSizeUnit(sizeVal.getUnit());
return out;
}
private static FontInfo parseFontName(FontInfo out, String arg) {
arg = arg.trim();
if (arg.length() > 0 && arg.charAt(0) == '/') {
arg = arg.substring(1);
}
if (arg.indexOf('/') != -1) {
arg = arg.substring(0, arg.indexOf('/')).trim();
} else {
int len = arg.length();
if (len > 3 && arg.charAt(len-4) == '.') {
arg = arg.substring(0, len-4);
}
}
out.setName(arg);
return out;
}
private static FontInfo parseFontFile(FontInfo out, String arg) {
arg = arg.trim();
if (arg.indexOf('/') != -1) {
arg = arg.substring(arg.indexOf('/'));
}
if (arg.length() > 0 && arg.charAt(0) == '/') {
arg = arg.substring(1);
} else {
arg = arg.indexOf("native:") == 0 ? arg :
arg.indexOf(".ttf") != arg.length()-4 ? arg + ".ttf" :
arg;
}
out.setFile(arg);
return out;
}
private static boolean isFontSizeArg(String arg) {
return arg != null && arg.length() > 0 && Character.isDigit(arg.charAt(0));
}
static FontInfo parseFont(FontInfo out, String font) {
if (font == null || font.trim().length() == 0) {
return null;
}
font = font.trim();
String[] args = Util.split(font, " ");
int len = args.length;
if (len == 1) {
String arg = args[0].trim();
if (arg.length() == 0) {
out.setSize(null);
out.setSizeUnit(UNIT_INHERIT);
out.setFile(null);
out.setName(null);
return out;
}
if (isFontSizeArg(arg)) {
// This is a size
parseFontSize(out, arg);
out.setName(null);
out.setFile(null);
return out;
} else {
out.setSizeUnit(UNIT_INHERIT);
out.setSize(null);
parseFontName(out, arg);
parseFontFile(out, arg);
}
} else if (len == 2) {
if (isFontSizeArg(args[0])) {
parseFontSize(out, args[0]);
parseFontName(out, args[1]);
parseFontFile(out, args[1]);
return out;
} else {
out.setSize(null);
out.setSizeUnit(UNIT_INHERIT);
parseFontName(out, args[0]);
parseFontFile(out, args[1]);
return out;
}
} else if (len == 3) {
parseFontSize(out, args[0]);
parseFontName(out, args[1]);
parseFontFile(out, args[2]);
return out;
} else {
throw new IllegalArgumentException("Failed to parse font");
}
return out;
}
static Font parseFont(Style baseStyle, String font) {
if (font == null || font.trim().length() == 0) {
return null;
}
FontInfo finfo = parseFont(new FontInfo(), font);
return finfo.createFont(baseStyle);
}
private static ScalarValue[] parseTRBLValue(String val) {
ScalarValue[] out = new ScalarValue[4];
String[] parts = Util.split(val, " ");
int len = parts.length;
switch (len) {
case 1:
ScalarValue v = parseSingleTRBLValue(parts[0]);
for (int i=0; i<4; i++) {
out[i] = v;
}
return out;
case 2: {
ScalarValue v1 = parseSingleTRBLValue(parts[0]);
ScalarValue v2 = parseSingleTRBLValue(parts[1]);
out[Component.TOP] = out[Component.BOTTOM] = v1;
out[Component.LEFT] = out[Component.RIGHT] = v2;
return out;
}
case 3: {
ScalarValue v1 = parseSingleTRBLValue(parts[0]);
ScalarValue v2 = parseSingleTRBLValue(parts[1]);
ScalarValue v3 = parseSingleTRBLValue(parts[2]);
out[Component.TOP] = v1;
out[Component.LEFT] = out[Component.RIGHT] = v2;
out[Component.BOTTOM] = v3;
return out;
}
case 4: {
ScalarValue v1 = parseSingleTRBLValue(parts[0]);
ScalarValue v2 = parseSingleTRBLValue(parts[1]);
ScalarValue v3 = parseSingleTRBLValue(parts[2]);
ScalarValue v4 = parseSingleTRBLValue(parts[3]);
out[Component.TOP] = v1;
out[Component.RIGHT] = v2;
out[Component.BOTTOM] = v3;
out[Component.LEFT] = v4;
return out;
}
}
return null;
}
private static ScalarValue parseSingleTRBLValue(String val) {
val = val.trim();
int plen = val.length();
StringBuilder sb = new StringBuilder();
ScalarValue out = new ScalarValue();
boolean parsedValue = false;
for (int i=0; i 0) ? Double.parseDouble(sb.toString()) : 0);
parsedValue = true;
sb.setLength(0);
sb.append(c);
} else {
sb.append(c);
}
}
if (parsedValue) {
String unitStr = sb.toString();
out.setUnit("mm".equals(unitStr) ? Style.UNIT_TYPE_DIPS :
"%".equals(unitStr) ? Style.UNIT_TYPE_SCREEN_PERCENTAGE :
"inherit".equals(unitStr) ? UNIT_INHERIT :
Style.UNIT_TYPE_PIXELS);
} else {
out.setUnit(Style.UNIT_TYPE_PIXELS);
out.setValue(Double.parseDouble(sb.toString()));
}
return out;
}
static Image parseBgImage(Resources theme, String val) {
if (val == null || val.trim().length() == 0) {
return null;
}
return getImage(theme, val);
}
private static Map bgTypes;
private static Map bgTypes() {
if (bgTypes == null) {
bgTypes = new HashMap();
Object[] types = new Object[] {
"image_aligned_bottom", (int)Style.BACKGROUND_IMAGE_ALIGNED_BOTTOM,
"image_aligned_top", (int)Style.BACKGROUND_IMAGE_ALIGNED_TOP,
"image_aligned_bottom_right", (int)Style.BACKGROUND_IMAGE_ALIGNED_BOTTOM_RIGHT,
"image_aligned_bottom_left", (int)Style.BACKGROUND_IMAGE_ALIGNED_BOTTOM_LEFT,
"image_aligned_top_right", (int)Style.BACKGROUND_IMAGE_ALIGNED_TOP_RIGHT,
"image_aligned_top_left", (int)Style.BACKGROUND_IMAGE_ALIGNED_TOP_LEFT,
"image_aligned_left", (int)Style.BACKGROUND_IMAGE_ALIGNED_LEFT,
"image_aligned_right", (int)Style.BACKGROUND_IMAGE_ALIGNED_RIGHT,
"image_aligned_center", (int)Style.BACKGROUND_IMAGE_ALIGNED_CENTER,
"image_scaled", (int)Style.BACKGROUND_IMAGE_SCALED,
"image_scaled_fill", (int)Style.BACKGROUND_IMAGE_SCALED_FILL,
"image_scaled_fit", (int)Style.BACKGROUND_IMAGE_SCALED_FIT,
"image_tile_both", (int)Style.BACKGROUND_IMAGE_TILE_BOTH,
"image_tile_horizontal", (int)Style.BACKGROUND_IMAGE_TILE_HORIZONTAL,
"image_tile_vertical", (int)Style.BACKGROUND_IMAGE_TILE_VERTICAL,
"image_tile_horizontal_align_bottom", (int)Style.BACKGROUND_IMAGE_TILE_HORIZONTAL_ALIGN_BOTTOM,
"image_tile_horizontal_align_top", (int)Style.BACKGROUND_IMAGE_TILE_HORIZONTAL_ALIGN_TOP,
"image_tile_horizontal_align_center", (int)Style.BACKGROUND_IMAGE_TILE_HORIZONTAL_ALIGN_CENTER,
"image_tile_vertical_align_left", (int)Style.BACKGROUND_IMAGE_TILE_VERTICAL_ALIGN_LEFT,
"image_tile_vertical_align_right", (int)Style.BACKGROUND_IMAGE_TILE_VERTICAL_ALIGN_RIGHT,
"image_tile_vertical_align_center", (int)Style.BACKGROUND_IMAGE_TILE_VERTICAL_ALIGN_CENTER,
"gradient_radial", (int)Style.BACKGROUND_GRADIENT_RADIAL,
"gradient_linear_horizontal", (int)Style.BACKGROUND_GRADIENT_LINEAR_HORIZONTAL,
"gradient_linear_vertical", (int)Style.BACKGROUND_GRADIENT_LINEAR_VERTICAL,
"none", (int)Style.BACKGROUND_NONE
};
int len = types.length;
for (int i=0; i getBackgroundTypes() {
ArrayList out = new ArrayList();
out.addAll(bgTypes().keySet());
Collections.sort(out, new CaseInsensitiveOrder());
return out;
}
private static Map flip(Map map) {
Map out = new HashMap();
for (Map.Entry e : map.entrySet()) {
out.put(e.getValue(), e.getKey());
}
return out;
}
static Integer parseBgType(String val) {
if (val == null || val.length() == 0) {
return null;
}
if (Character.isDigit(val.charAt(0))) {
return Integer.parseInt(val);
}
Map bgTypes = bgTypes();
if (bgTypes.containsKey(val)) {
return bgTypes.get(val);
}
return null;
}
/**
* Checks if a string is a valid scalar value. A scalar value should be in the
* format {@literal } where {@literal } is an integer
* or decimal number, and {@literal } is a unit - one of {@literal mm}, {@literal px}, or {@literal %}.
*
* There is one special value: "inherit" which indicates that the scalar value just inherits from its
* parent.
* @param val String value to validate.
* @return True if the value is a valid scalar value.
*/
public static boolean validateScalarValue(String val) {
try {
parseSingleTRBLValue(val);
return true;
} catch (Throwable t) {
return false;
}
}
/**
* Parses a string into a scalar value. A scalar value should be in the
* format {@literal } where {@literal } is an integer
* or decimal number, and {@literal } is a unit - one of {@literal mm}, {@literal px}, or {@literal %}.
*
* There is one special value: "inherit" which indicates that the scalar value just inherits from its
* parent.
* @param val String that should be a valid scalar value.
* @return
*/
public static ScalarValue parseScalarValue(String val) {
return parseSingleTRBLValue(val);
}
/**
* Gets a list of the background types that are supported.
* @return
*/
public static List getSupportedBackgroundTypes() {
ArrayList out = new ArrayList();
out.addAll(bgTypes.keySet());
return out;
}
private static String format(double d, int decimalPlaces) {
for (int i=0; i decimalPlaces) {
dStr = dStr.substring(0, dStr.length() - (decLen - decimalPlaces));
}
}
return dStr;
}
}