/* This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV This program is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program; if not, see http://www.gnu.org/licenses or write to the Free Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 */ package com.servoy.j2db.util; import java.awt.Color; import java.awt.Font; import java.awt.Insets; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.StringTokenizer; import javax.swing.BorderFactory; import javax.swing.SwingConstants; import javax.swing.border.BevelBorder; import javax.swing.border.Border; import javax.swing.border.EtchedBorder; import javax.swing.text.AttributeSet; import javax.swing.text.MutableAttributeSet; import javax.swing.text.html.CSS; import javax.swing.text.html.CSS.Attribute; import javax.swing.text.html.StyleSheet; import org.xhtmlrenderer.css.constants.CSSName; import com.servoy.j2db.util.gui.CustomBevelBorder; import com.servoy.j2db.util.gui.CustomEtchedBorder; import com.servoy.j2db.util.gui.RoundedBorder; import com.servoy.j2db.util.gui.SpecialMatteBorder; /** * @author jblok * @deprecated only keep for backwards compatibility of plugins * Enhanced/fixed subclass for proper behaviour. */ @Deprecated public class FixedStyleSheet extends StyleSheet { public FixedStyleSheet() { super(); } /* * @see javax.swing.text.StyleContext#getFont(java.lang.String, int, int) */ @Override public Font getFont(AttributeSet a) { Font font = super.getFont(a); Object family = a.getAttribute(CSS.Attribute.FONT_FAMILY); if (family != null && family.toString().indexOf(font.getName()) == -1) { font = PersistHelper.createFont(family.toString(), font.getStyle(), font.getSize()); } return font; } // is not possible :-(, the Boxpainter constructor is not visible // public BoxPainter getBoxPainter(AttributeSet a) // { // return new BoxPainter(a, css, this); // } public Insets getMargin(AttributeSet a) { if (hasMargin(a)) { float top = getLength(a.getAttribute(CSS.Attribute.MARGIN_TOP)); float bottom = getLength(a.getAttribute(CSS.Attribute.MARGIN_BOTTOM)); float left = getLength(a.getAttribute(CSS.Attribute.MARGIN_LEFT)); float right = getLength(a.getAttribute(CSS.Attribute.MARGIN_RIGHT)); return new Insets(top < 0 ? 0 : (int)top, left < 0 ? 0 : (int)left, bottom < 0 ? 0 : (int)bottom, right < 0 ? 0 : (int)right); } return null; } public Border getBorder(AttributeSet a) { Border b = null; // Initial values. Object borderStyle = null; Object borderColor = null; float top = getLength(null); float right = getLength(null); float bottom = getLength(null); float left = getLength(null); // Try to use the unexpanded "border" attribute, if any. Object unexpandedBorder = a.getAttribute(CSS.Attribute.BORDER); if (unexpandedBorder != null) { StringTokenizer st = new StringTokenizer(unexpandedBorder.toString()); // Some heuristics here. May be too permissive, but should work in the // general case. while (st.hasMoreTokens()) { String tok = st.nextToken(); // If we got digits in the token, then it refers to border size. if (tok.matches("^[0-9]+.*")) //$NON-NLS-1$ { top = right = bottom = left = getLength(tok); } // If it is a border style keyword, use it accordingly. else if (Arrays.asList(IStyleSheet.BORDER_STYLES).contains(tok)) { borderStyle = tok; } // If "transparent" then transparent. else if (tok.equals(IStyleSheet.COLOR_TRANSPARENT)) { borderColor = IStyleSheet.COLOR_TRANSPARENT; } // Otherwise assume it is a color. else { Color c = PersistHelper.createColor(tok); if (c != null) borderColor = tok; } } } // If any specific properties are set, they will just override what was // extracted from the unexpanded "border" attribute. if (a.isDefined(CSS.Attribute.BORDER_STYLE)) borderStyle = a.getAttribute(CSS.Attribute.BORDER_STYLE); if (a.isDefined(CSS.Attribute.BORDER_COLOR)) borderColor = a.getAttribute(CSS.Attribute.BORDER_COLOR); if (a.isDefined(CSS.Attribute.BORDER_TOP_WIDTH)) top = getLength(a.getAttribute(CSS.Attribute.BORDER_TOP_WIDTH)); if (a.isDefined(CSS.Attribute.BORDER_RIGHT_WIDTH)) right = getLength(a.getAttribute(CSS.Attribute.BORDER_RIGHT_WIDTH)); if (a.isDefined(CSS.Attribute.BORDER_BOTTOM_WIDTH)) bottom = getLength(a.getAttribute(CSS.Attribute.BORDER_BOTTOM_WIDTH)); if (a.isDefined(CSS.Attribute.BORDER_LEFT_WIDTH)) left = getLength(a.getAttribute(CSS.Attribute.BORDER_LEFT_WIDTH)); if (borderStyle == null) { // TODO this should be fixed when we compile against java 7. // java 7 expands the complete border: Enumeration< ? > attributes = a.getAttributeNames(); while (attributes.hasMoreElements()) { Object element = attributes.nextElement(); if (element != null && element.toString().startsWith("border-top-style")) { borderStyle = a.getAttribute(element); break; } } } if (borderStyle == null && (borderColor != null || top >= 0 || right >= 0 || bottom >= 0 || left >= 0)) { borderStyle = IStyleSheet.BORDER_STYLE_SOLID; } if (borderStyle != null) { Color[] colors = getBorderColor(borderColor, a); String bstyle = borderStyle.toString(); if (a.isDefined(CSSName.BORDER_TOP_LEFT_RADIUS.toString())) { top = makeSizeSave(top); right = makeSizeSave(right); bottom = makeSizeSave(bottom); left = makeSizeSave(left); colors = expandColors(colors); b = new RoundedBorder(top, left, bottom, right, colors[0], colors[3], colors[2], colors[1]); float[] radius = new float[8]; parseCornerBorderRadius(a, radius, 0, CSSName.BORDER_TOP_LEFT_RADIUS.toString()); parseCornerBorderRadius(a, radius, 1, CSSName.BORDER_TOP_RIGHT_RADIUS.toString()); parseCornerBorderRadius(a, radius, 2, CSSName.BORDER_BOTTOM_RIGHT_RADIUS.toString()); parseCornerBorderRadius(a, radius, 3, CSSName.BORDER_BOTTOM_LEFT_RADIUS.toString()); ((RoundedBorder)b).setRoundingRadius(radius); ((RoundedBorder)b).setBorderStyles(new String[] { (String)a.getAttribute(CSSName.BORDER_TOP_STYLE.toString()), (String)a.getAttribute(CSSName.BORDER_LEFT_STYLE.toString()), (String)a.getAttribute(CSSName.BORDER_BOTTOM_STYLE.toString()), (String)a.getAttribute(CSSName.BORDER_RIGHT_STYLE.toString()) }); } else if (bstyle.equals(IStyleSheet.BORDER_STYLE_INSET) || bstyle.equals(IStyleSheet.BORDER_STYLE_OUTSET)) { int style = BevelBorder.LOWERED; if (bstyle.equals(IStyleSheet.BORDER_STYLE_OUTSET)) style = BevelBorder.RAISED; Insets customBorderInsets = null; if (top != -1.0 || right != -1.0 || bottom != -1.0 || left != -1.0) { top = makeSizeSave(top); right = makeSizeSave(right); bottom = makeSizeSave(bottom); left = makeSizeSave(left); customBorderInsets = new Insets((int)top, (int)left, (int)bottom, (int)right); } if (colors != null && colors.length > 0) { if (colors.length == 1 || (colors.length == 4 && Utils.equalObjects(colors[0], colors[1]) && Utils.equalObjects(colors[0], colors[2]) && Utils.equalObjects( colors[0], colors[3]))) { // this tries to do the same thing as the web does.. b = new CustomBevelBorder(style, colors[0], customBorderInsets); } else if (colors.length == 2) { b = new CustomBevelBorder(style, colors[0], colors[1], customBorderInsets); } else if (colors.length > 3) { b = new CustomBevelBorder(style, colors[0], colors[1], colors[2], colors[3], customBorderInsets); } } else { b = new CustomBevelBorder(style, customBorderInsets); } } else if (bstyle.equals(IStyleSheet.BORDER_STYLE_NONE)) { b = BorderFactory.createEmptyBorder(); } else if (bstyle.equals(IStyleSheet.BORDER_STYLE_DOUBLE)) { top = makeSizeSave(top); right = makeSizeSave(right); bottom = makeSizeSave(bottom); left = makeSizeSave(left); b = BorderFactory.createEmptyBorder((int)top, (int)left, (int)bottom, (int)right); } else if (bstyle.equals(IStyleSheet.BORDER_STYLE_GROOVE) || bstyle.equals(IStyleSheet.BORDER_STYLE_RIDGE)) { int style = EtchedBorder.LOWERED; if (bstyle.equals(IStyleSheet.BORDER_STYLE_RIDGE)) style = EtchedBorder.RAISED; Insets customBorderInsets = null; if (top != -1.0 || right != -1.0 || bottom != -1.0 || left != -1.0) { top = makeSizeSave(top); right = makeSizeSave(right); bottom = makeSizeSave(bottom); left = makeSizeSave(left); customBorderInsets = new Insets((int)top, (int)left, (int)bottom, (int)right); } if (colors != null && colors.length > 0) { if (colors.length == 1 || (colors.length == 4 && Utils.equalObjects(colors[0], colors[1]) && Utils.equalObjects(colors[0], colors[2]) && Utils.equalObjects( colors[0], colors[3]))) { // this tries to do the same thing as the web does.. b = new CustomEtchedBorder(style, colors[0], customBorderInsets); } else if (colors.length >= 2) { b = new CustomEtchedBorder(style, colors[0], colors[1], customBorderInsets); } } else { b = new CustomEtchedBorder(style, customBorderInsets); } } else if (bstyle.equals(IStyleSheet.BORDER_STYLE_SOLID) || bstyle.equals(IStyleSheet.BORDER_STYLE_DOTTED) || bstyle.equals(IStyleSheet.BORDER_STYLE_DASHED)) { // int bw = (int) getLength(CSS.Attribute.BORDER_WIDTH, a); // int colorCount = (colors == null ? 0 : colors.length); colors = expandColors(colors); // Object obj = a.getAttribute(CSS.Attribute.BORDER_WIDTH); if (bstyle.equals(IStyleSheet.BORDER_STYLE_SOLID)) { if (borderColor != null && IStyleSheet.COLOR_TRANSPARENT.equals(borderColor.toString())) { top = makeSizeSave(top); right = makeSizeSave(right); bottom = makeSizeSave(bottom); left = makeSizeSave(left); b = BorderFactory.createEmptyBorder((int)top, (int)left, (int)bottom, (int)right); } else if (colors != null) { if (top == -1f && right == -1f && bottom == -1f && left == -1f)//is undefined, make 1 px { b = BorderFactory.createLineBorder(colors[0]); } else if (top == 0f && right == 0f && bottom == 0f && left == 0f)//is contradiction solid but width 0 make empty border { b = BorderFactory.createEmptyBorder(); } else { top = makeSizeSave(top); right = makeSizeSave(right); bottom = makeSizeSave(bottom); left = makeSizeSave(left); b = new SpecialMatteBorder(top, left, bottom, right, colors[0], colors[3], colors[2], colors[1]); } } } else if (bstyle.equals(IStyleSheet.BORDER_STYLE_DASHED)) { if (top <= 0 && left <= 0 && bottom <= 0 && right <= 0) { top = left = bottom = right = 1; } b = new SpecialMatteBorder(top, left, bottom, right, colors[0], colors[3], colors[2], colors[1]); ((SpecialMatteBorder)b).setDashPattern(new float[] { 3, 3 }); } else if (bstyle.equals(IStyleSheet.BORDER_STYLE_DOTTED)) { if (top <= 0 && left <= 0 && bottom <= 0 && right <= 0) { top = left = bottom = right = 1; } b = new SpecialMatteBorder(top, left, bottom, right, colors[0], colors[3], colors[2], colors[1]); ((SpecialMatteBorder)b).setDashPattern(new float[] { 1, 1 }); } } } return b; } private void parseCornerBorderRadius(AttributeSet a, float[] values, int index, String attributeName) { String value = (String)a.getAttribute(attributeName); if (value != null) { value = value.trim(); StringTokenizer tokenizer = new StringTokenizer(value, " "); if (tokenizer.countTokens() == 2) { values[index] = getLength(tokenizer.nextElement()); values[index + 4] = getLength(tokenizer.nextElement()); } } } private float makeSizeSave(float f) { if (f < 0f) f = 0f; return f; } private Color[] expandColors(Color[] colors) { if (colors == null || colors.length == 0) { colors = new Color[] { Color.black, Color.black }; } else if (colors.length == 1) { colors = new Color[] { colors[0], colors[0] }; } Color topC = colors[0]; Color rightC = colors[1]; Color bottomC = colors[0]; Color leftC = colors[1]; if (colors.length > 2) { bottomC = colors[2]; } if (colors.length > 3) { leftC = colors[3]; } return new Color[] { topC, rightC, bottomC, leftC }; } /** * Fetches the color to use for borders. This will either be * the value specified by the border-color attribute (which * is not inherited), or it will default to the color attribute * (which is inherited). */ @SuppressWarnings("nls") private Color[] getBorderColor(Object obj, AttributeSet a) { if (obj != null) { ArrayList<Color> colors = new ArrayList<Color>(); String val = obj.toString(); StringTokenizer tk = new StringTokenizer(val, " "); while (tk.hasMoreTokens()) { String token = tk.nextToken(); colors.add(PersistHelper.createColor(token)); } return colors.toArray(new Color[colors.size()]); } else { // TODO this should be fixed when we compile against java 7. Color top = null; Color right = null; Color bottom = null; Color left = null; // java 7 expands the complete border: Enumeration< ? > attributes = a.getAttributeNames(); while (attributes.hasMoreElements()) { Object element = attributes.nextElement(); Object value = a.getAttribute(element); String colorString = value != null ? value.toString() : null; if (element.toString().equals("border-top-color")) { top = PersistHelper.createColor(colorString); } else if (element.toString().equals("border-right-color")) { right = PersistHelper.createColor(colorString); } else if (element.toString().equals("border-bottom-color")) { bottom = PersistHelper.createColor(colorString); } else if (element.toString().equals("border-left-color")) { left = PersistHelper.createColor(colorString); } } if (top != null && right != null && bottom != null && left != null) { return new Color[] { top, right, bottom, left }; } else { ArrayList<Color> colors = new ArrayList<Color>(); if (top != null) colors.add(top); if (right != null) colors.add(right); if (bottom != null) colors.add(bottom); if (left != null) colors.add(left); if (colors.size() > 0) return colors.toArray(new Color[colors.size()]); } } return null; } /** * Get the length/width in px/pt * @param key * @param a * @return -1 if undefined */ public float getLength(Object obj) { if (obj != null) { String val = obj.toString(); if (val.endsWith("px") || val.endsWith("pt")) //$NON-NLS-1$ //$NON-NLS-2$ { val = val.substring(0, val.length() - 2); } return Utils.getAsFloat(val); } return -1f; } public int getHAlign(AttributeSet a) { Object obj = a.getAttribute(CSS.Attribute.TEXT_ALIGN); return getHAlign((String)obj); } public int getHAlign(String val) { if ("left".equals(val)) //$NON-NLS-1$ { return SwingConstants.LEFT; } else if ("center".equals(val)) //$NON-NLS-1$ { return SwingConstants.CENTER; } else if ("right".equals(val)) //$NON-NLS-1$ { return SwingConstants.RIGHT; } else if ("justify".equals(val)) //$NON-NLS-1$ { return SwingConstants.CENTER; } return -1; } public int getVAlign(AttributeSet a) { Object obj = a.getAttribute(CSS.Attribute.VERTICAL_ALIGN); return getVAlign((String)obj); } public int getVAlign(String val) { if ("top".equals(val)) //$NON-NLS-1$ { return SwingConstants.TOP; } else if ("text-top".equals(val)) //$NON-NLS-1$ { return SwingConstants.TOP; } else if ("super".equals(val)) //$NON-NLS-1$ { return SwingConstants.TOP; } else if ("middle".equals(val)) //$NON-NLS-1$ { return SwingConstants.CENTER; } else if ("baseline".equals(val)) //$NON-NLS-1$ { return SwingConstants.CENTER; } else if ("bottom".equals(val)) //$NON-NLS-1$ { return SwingConstants.BOTTOM; } else if ("text-bottom".equals(val)) //$NON-NLS-1$ { return SwingConstants.BOTTOM; } else if ("sub".equals(val)) //$NON-NLS-1$ { return SwingConstants.BOTTOM; } return -1; } @Override public void addCSSAttribute(MutableAttributeSet attr, CSS.Attribute key, String value) { String newValue = expandColorValue(key, value); if (key == CSS.Attribute.BACKGROUND_COLOR && IStyleSheet.COLOR_TRANSPARENT.equalsIgnoreCase(newValue)) attr.addAttribute(key, newValue); else super.addCSSAttribute(attr, key, newValue); } /* * If the attribute is a color property then check whether it is in sort format or not. Short format means #abc which should expand to #aabbcc. If in short * format then expand it. */ protected String expandColorValue(CSS.Attribute key, String value) { if (key != null && key.toString().matches(".*color.*") && value.startsWith("#") && value.length() == 4) //$NON-NLS-1$//$NON-NLS-2$ { return new StringBuilder("#").append(value.charAt(1)).append(value.charAt(1)).append(value.charAt(2)).append(value.charAt(2)).append( //$NON-NLS-1$ value.charAt(3)).append(value.charAt(3)).toString(); } else return value; } public boolean hasBorder(AttributeSet s) { return hasAttributes(s, ServoyStyleSheet.borderAttributes); } public boolean hasMargin(AttributeSet s) { return hasAttributes(s, ServoyStyleSheet.marginAttributes); } public boolean hasFont(AttributeSet s) { return hasAttributes(s, ServoyStyleSheet.fontAttributes); } private boolean hasAttributes(AttributeSet s, Attribute[] attrs) { for (Attribute a : attrs) { if (s.getAttribute(a) != null) return true; } return false; } @Override public Color getBackground(AttributeSet a) { Object sbackground_color = a.getAttribute(CSS.Attribute.BACKGROUND_COLOR); if (sbackground_color != null) { if (!IStyleSheet.COLOR_TRANSPARENT.equals(sbackground_color.toString())) { return super.getBackground(a); } else { return PersistHelper.COLOR_TRANSPARENT; } } return null; } }