0001: /*
0002: * Copyright 2000,2005 wingS development team.
0003: *
0004: * This file is part of wingS (http://wingsframework.org).
0005: *
0006: * wingS is free software; you can redistribute it and/or modify
0007: * it under the terms of the GNU Lesser General Public License
0008: * as published by the Free Software Foundation; either version 2.1
0009: * of the License, or (at your option) any later version.
0010: *
0011: * Please see COPYING for the complete licence.
0012: */
0013: package org.wings.plaf.css;
0014:
0015: import org.apache.commons.logging.Log;
0016: import org.apache.commons.logging.LogFactory;
0017: import org.wings.*;
0018: import org.wings.border.SAbstractBorder;
0019: import org.wings.border.SDefaultBorder;
0020: import org.wings.border.SBorder;
0021: import org.wings.externalizer.ExternalizeManager;
0022: import org.wings.header.JavaScriptHeader;
0023: import org.wings.header.Link;
0024: import org.wings.header.Script;
0025: import org.wings.header.StyleSheetHeader;
0026: import org.wings.io.Device;
0027: import org.wings.io.NullDevice;
0028: import org.wings.io.StringBuilderDevice;
0029: import org.wings.resource.*;
0030: import org.wings.script.JavaScriptDOMListener;
0031: import org.wings.script.JavaScriptEvent;
0032: import org.wings.script.JavaScriptListener;
0033: import org.wings.script.ScriptListener;
0034: import org.wings.session.BrowserType;
0035: import org.wings.session.Session;
0036: import org.wings.session.SessionManager;
0037: import org.wings.style.Style;
0038: import org.wings.style.CSSStyle;
0039: import org.wings.style.CSSAttributeSet;
0040: import org.wings.util.SStringBuilder;
0041:
0042: import java.awt.*;
0043: import java.io.BufferedReader;
0044: import java.io.IOException;
0045: import java.io.InputStream;
0046: import java.io.InputStreamReader;
0047: import java.util.ArrayList;
0048: import java.util.HashMap;
0049: import java.util.HashSet;
0050: import java.util.Iterator;
0051: import java.util.List;
0052: import java.util.Map;
0053: import java.util.Set;
0054: import java.util.StringTokenizer;
0055:
0056: /**
0057: * Utils.java
0058: * <p/>
0059: * Helper class that collects static methods usable from CGs.
0060: *
0061: * @author <a href="mailto:mreinsch@to.com">Michael Reinsch</a>
0062: */
0063: public final class Utils {
0064: /**
0065: * Apache jakarta commons logger
0066: */
0067: private static final Log log = LogFactory.getLog(Utils.class);
0068:
0069: /**
0070: * Print debug information in generated HTML
0071: */
0072: public final static boolean PRINT_DEBUG;
0073: public final static boolean PRINT_PRETTY;
0074:
0075: public final static String HEADER_LOADED_CALLBACK = "if (typeof wingS != \"undefined\") {\n"
0076: + " wingS.global.finishedLoadingHeader();\n" + "}";
0077:
0078: static {
0079: Session session = SessionManager.getSession();
0080: // Respect settings from resource.properties
0081: Boolean printDebug = (Boolean) ResourceManager.getObject(
0082: "SComponent.printDebug", Boolean.class);
0083: Boolean printPretty = (Boolean) ResourceManager.getObject(
0084: "SComponent.printPretty", Boolean.class);
0085: // May be overriden in i.e. web.xml. Hopefully we touch the class inside a session for the first time
0086: if (session != null) {
0087: if (session.getProperty("SComponent.printDebug") != null)
0088: printDebug = Boolean.valueOf((String) session
0089: .getProperty("SComponent.printDebug"));
0090: if (session.getProperty("SComponent.printPretty") != null)
0091: printPretty = Boolean.valueOf((String) session
0092: .getProperty("SComponent.printPretty"));
0093: }
0094: PRINT_DEBUG = printDebug.booleanValue();
0095: PRINT_PRETTY = printPretty.booleanValue();
0096: }
0097:
0098: protected final static char[] hexDigits = { '0', '1', '2', '3',
0099: '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
0100:
0101: private Utils() {
0102: }
0103:
0104: /**
0105: * Default list of javascript events to exlcude in {@link #writeEvents(org.wings.io.Device, org.wings.SComponent, String[])}
0106: */
0107: public final static String[] EXCLUDE_ON_CLICK = new String[] { JavaScriptEvent.ON_CLICK };
0108:
0109: /**
0110: * Renders a container using its Layout manager or fallback just one after another.
0111: */
0112: public static void renderContainer(Device d, SContainer c)
0113: throws IOException {
0114: final SLayoutManager layout = c.getLayout();
0115:
0116: if (layout == null) {
0117: d.print("<tbody><tr><td>");
0118: // just write out the components one after another
0119: for (int i = 0; i < c.getComponentCount(); i++) {
0120: c.getComponent(i).write(d);
0121: }
0122: d.print("</td></tr></tbody>");
0123: } else {
0124: layout.write(d);
0125: }
0126: }
0127:
0128: /**
0129: * Render inline event listeners attached to the passed component exlucding types of suppressed listeners
0130: *
0131: * @param device output device
0132: * @param c component to retrieve listeners from
0133: * @param suppressScriptListenerTypes Array of String i.e. <code>new String[] { JavaScriptEvent.ON_CLICK } )</code>
0134: */
0135: public static void writeEvents(final Device device,
0136: final SComponent c,
0137: final String[] suppressScriptListenerTypes)
0138: throws IOException {
0139: if (!c.isEnabled())
0140: return;
0141:
0142: Set<String> types = new HashSet<String>();
0143: // Create set of strings (in lower case) defining the event types to be suppressed
0144: if (suppressScriptListenerTypes != null
0145: && suppressScriptListenerTypes.length > 0) {
0146: for (String suppressScriptListenerType : suppressScriptListenerTypes) {
0147: types.add(suppressScriptListenerType.toLowerCase());
0148: }
0149: }
0150: ScriptListener[] listeners = c.getScriptListeners();
0151: if (listeners.length > 0) {
0152: Map<String, String> eventScripts = new HashMap<String, String>();
0153: // Fill map with script codes grouped by event type (key)
0154: for (final ScriptListener script : listeners) {
0155: if (types.contains(script.getEvent().toLowerCase())) {
0156: continue;
0157: }
0158: // If its a DOM event we are finished here
0159: if (script instanceof JavaScriptDOMListener) {
0160: continue;
0161: }
0162: final String event = script.getEvent();
0163: String eventScriptCode = script.getCode();
0164:
0165: if (event == null || event.length() == 0
0166: || eventScriptCode == null
0167: || eventScriptCode.length() == 0) {
0168: continue;
0169: }
0170:
0171: if (eventScripts.containsKey(event)) {
0172: String savedEventScriptCode = eventScripts
0173: .get(event);
0174: eventScriptCode = savedEventScriptCode
0175: + (savedEventScriptCode.trim()
0176: .endsWith(";") ? "" : ";")
0177: + eventScriptCode;
0178: }
0179: eventScripts.put(event, eventScriptCode);
0180: }
0181:
0182: // Print map of script codes grouped by event type (key)
0183: for (final String event : eventScripts.keySet()) {
0184: final String code = eventScripts.get(event);
0185: Utils.optAttribute(device, event, code);
0186: }
0187: }
0188: }
0189:
0190: /**
0191: * Returns the according event ID for the given component.
0192: */
0193: public static String event(SComponent component) {
0194: if (component instanceof SClickable)
0195: return ((SClickable) component).getEventTarget()
0196: .getLowLevelEventId();
0197: else
0198: return component.getLowLevelEventId();
0199: }
0200:
0201: /**
0202: * HTML allows 4 values for the horizontal align property of a div element.
0203: *
0204: * @param d Output
0205: * @param align Please refer {@link SConstants}
0206: * @throws IOException
0207: */
0208: public static void printDivHorizontalAlignment(Device d, int align)
0209: throws IOException {
0210: printTableHorizontalAlignment(d, align);
0211: }
0212:
0213: /**
0214: * Horizontal alignment for TABLE cells. i.e. <code>align="center"</code>
0215: */
0216: private static void printTableHorizontalAlignment(final Device d,
0217: final int align) throws IOException {
0218: if (align == SConstants.NO_ALIGN) {
0219: // d.print(" align=\"left\"");
0220: } else if (align == SConstants.LEFT) {
0221: d.print(" align=\"left\"");
0222: } else if (align == SConstants.CENTER) {
0223: d.print(" align=\"center\"");
0224: } else if (align == SConstants.RIGHT) {
0225: d.print(" align=\"right\"");
0226: } else if (align == SConstants.JUSTIFY) {
0227: d.print(" align=\"justify\"");
0228: }
0229: }
0230:
0231: /**
0232: * HTML allows 4 values for the vertical align property of a div element.
0233: *
0234: * @param d Output
0235: * @param align Please refer {@link SConstants}
0236: * @throws IOException
0237: */
0238: public static void printDivVerticalAlignment(Device d, int align)
0239: throws IOException {
0240: printTableVerticalAlignment(d, align);
0241: }
0242:
0243: /**
0244: * Vertical alignment for TABLE cells. i.e. <code>valign="top"</code>
0245: */
0246: private static void printTableVerticalAlignment(Device d, int align)
0247: throws IOException {
0248: if (align == SConstants.NO_ALIGN) {
0249: //d.print(" valign=\"center\"");
0250: } else if (align == SConstants.CENTER) {
0251: d.print(" valign=\"middle\"");
0252: } else if (align == SConstants.TOP) {
0253: d.print(" valign=\"top\"");
0254: } else if (align == SConstants.BOTTOM) {
0255: d.print(" valign=\"bottom\"");
0256: } else if (align == SConstants.BASELINE) {
0257: d.print(" valign=\"baseline\"");
0258: }
0259: }
0260:
0261: /**
0262: * Renders the alignment commands for a table cell (horzontal and vertical).
0263: * To ensure a consistent behaviour you have to pass the default alignment applied for <code>SConstants.NO_ALIGN</code>.
0264: *
0265: * @param defaultHorizontal default horizontal alignment to use is not aligned
0266: * @param defaultVertical default vertical alignment to use if component is not aligned
0267: */
0268: public static void printTableCellAlignment(final Device d,
0269: final SComponent c, final int defaultHorizontal,
0270: final int defaultVertical) throws IOException {
0271: if (c != null) {
0272: final int horizontalAlignment = c.getHorizontalAlignment();
0273: final int verticalAlignment = c.getVerticalAlignment();
0274: printTableHorizontalAlignment(
0275: d,
0276: horizontalAlignment != SConstants.NO_ALIGN ? horizontalAlignment
0277: : defaultHorizontal);
0278: printTableVerticalAlignment(
0279: d,
0280: verticalAlignment != SConstants.NO_ALIGN ? verticalAlignment
0281: : defaultVertical);
0282: }
0283: }
0284:
0285: public static String toColorString(int rgb) {
0286: char[] buf = new char[6];
0287: int digits = 6;
0288: do {
0289: buf[--digits] = hexDigits[rgb & 15];
0290: rgb >>>= 4;
0291: } while (digits != 0);
0292:
0293: return new String(buf);
0294: }
0295:
0296: public static String toColorString(java.awt.Color c) {
0297: return toColorString(c.getRGB());
0298: }
0299:
0300: /**
0301: * Generates a SStringBuilder containing inlined CSS styles for the following properties of a SComponent:
0302: * <p><ul><li>Preffered Size</li><li>Font</li><li>Background- and Foregroud color.</li></ul>
0303: *
0304: * @param component Component to grab parameters from.
0305: */
0306: public static SStringBuilder generateCSSComponentInlineStyle(
0307: SComponent component) {
0308: final SStringBuilder styleString = new SStringBuilder();
0309: appendCSSInlineSize(styleString, component);
0310: appendCSSComponentInlineColorStyle(styleString, component);
0311: appendCSSComponentInlineFontStyle(styleString, component);
0312: return styleString;
0313: }
0314:
0315: /**
0316: * Append a inline CSS style definition for the passed component of the aspect foreground- and background color.
0317: *
0318: * @param styleString SStringBuilder to append to
0319: * @param component Component to use as style source
0320: * @return The passed styleString
0321: */
0322: public static SStringBuilder appendCSSComponentInlineColorStyle(
0323: SStringBuilder styleString, final SComponent component) {
0324: if (component != null) {
0325: if (component.getBackground() != null) {
0326: styleString.append("background-color:#").append(
0327: toColorString(component.getBackground()))
0328: .append(";");
0329: }
0330:
0331: if (component.getForeground() != null) {
0332: styleString.append("color:#").append(
0333: toColorString(component.getForeground()))
0334: .append(";");
0335: }
0336: }
0337: return styleString;
0338: }
0339:
0340: /**
0341: * Append a inline CSS style definition for the passed component of the aspect font properties.
0342: *
0343: * @param styleString SStringBuilder to append to
0344: * @param component Component to use as style source
0345: * @return The passed styleString
0346: */
0347: public static SStringBuilder appendCSSComponentInlineFontStyle(
0348: final SStringBuilder styleString, final SComponent component) {
0349: if (component != null && component.getFont() != null) {
0350: final SFont font = component.getFont();
0351: styleString.append("font-size:").append(font.getSize())
0352: .append("pt;");
0353: styleString.append("font-style:").append(
0354: (font.getStyle() & SFont.ITALIC) > 0 ? "italic;"
0355: : "normal;");
0356: styleString.append("font-weight:").append(
0357: (font.getStyle() & SFont.BOLD) > 0 ? "bold;"
0358: : "normal;");
0359: styleString.append("font-family:").append(font.getFace())
0360: .append(";");
0361: }
0362: return styleString;
0363: }
0364:
0365: /**
0366: * Appends a CSS inline style string for the preferred size of the passed component to the passed stringbuffer.
0367: * <p>Sample: <code>width:100%;heigth=15px"</code>
0368: */
0369: public static SStringBuilder appendCSSInlineSize(
0370: SStringBuilder styleString, SComponent component) {
0371: if (component == null)
0372: return styleString;
0373: SDimension preferredSize = component.getPreferredSize();
0374: if (preferredSize != null) {
0375: boolean msie = isMSIE(component);
0376: if (msie && "px".equals(preferredSize.getWidthUnit())) {
0377: int oversize = calculateHorizontalOversize(component,
0378: false);
0379: styleString.append("width:").append(
0380: preferredSize.getWidthInt() - oversize).append(
0381: "px;");
0382: } else if (!SDimension.AUTO.equals(preferredSize
0383: .getWidthUnit()))
0384: styleString.append("width:").append(
0385: preferredSize.getWidth()).append(';');
0386:
0387: if (msie && "px".equals(preferredSize.getHeightUnit())) {
0388: int oversize = calculateVerticalOversize(component,
0389: false);
0390: styleString.append("height:").append(
0391: preferredSize.getHeightInt() - oversize)
0392: .append("px;");
0393: } else if (!SDimension.AUTO.equals(preferredSize
0394: .getHeightUnit()))
0395: styleString.append("height:").append(
0396: preferredSize.getHeight()).append(';');
0397: }
0398: return styleString;
0399: }
0400:
0401: public static SStringBuilder generateCSSInlineBorder(
0402: SStringBuilder styles, int borderSize) {
0403: if (borderSize > 0) {
0404: styles.append("border:").append(borderSize).append(
0405: "px solid black;");
0406: } else {
0407: //styleString.append("border:none;"); Not necessary. Default
0408: }
0409: return styles;
0410: }
0411:
0412: /**
0413: * Prints a HTML style attribute with widht/height of 100% if the passed dimension defines a height or width..
0414: * <p>Sample: <code> style="width:100%;"</code>
0415: * <p/>
0416: * <p>This is typicall needed to stretch inner HTML element to expand to the full dimenstion defined
0417: * on an outer, sized HTML element. Otherwise the component would appear to small (as size is applied only
0418: * on the invisible outer limiting element)
0419: *
0420: * @param device Device to print to
0421: * @param preferredSize trigger dimension
0422: */
0423: public static void printCSSInlineFullSize(Device device,
0424: SDimension preferredSize) throws IOException {
0425: if ((preferredSize != null)
0426: && (!SDimension.AUTO.equals(preferredSize.getWidth()) || !SDimension.AUTO
0427: .equals(preferredSize.getHeight()))) {
0428: // opera doesn't show height 100% when parent has no defined height
0429: if (!SDimension.AUTO.equals(preferredSize.getHeight())) {
0430: device.print(" style=\"width:100%;height:100%\"");
0431: } else {
0432: device.print(" style=\"width:100%\"");
0433: }
0434: }
0435: }
0436:
0437: /**
0438: * Prints a HTML style attribute with widht/height of 100% if the passed dimension defines a height or width..
0439: * <p>Sample: <code> style="width:100%;"</code>
0440: * <p/>
0441: * <p>This is typicall needed to stretch inner HTML element to expand to the full dimenstion defined
0442: * on an outer, sized HTML element. Otherwise the component would appear to small (as size is applied only
0443: * on the invisible outer limiting element)
0444: *
0445: * @param pSStringBuilder buffer to append to
0446: * @param pComponent preferredSize trigger dimension
0447: */
0448: public static void appendCSSInlineFullSize(
0449: SStringBuilder pSStringBuilder, SComponent pComponent) {
0450: SDimension preferredSize = pComponent.getPreferredSize();
0451: if (preferredSize != null
0452: && (!SDimension.AUTO.equals(preferredSize.getWidth()) || !SDimension.AUTO
0453: .equals(preferredSize.getHeight()))) {
0454: pSStringBuilder.append("width:100%;height:100%;");
0455: }
0456: }
0457:
0458: /**
0459: * Writes an {X|HT}ML quoted string according to RFC 1866.
0460: * '"', '<', '>', '&' become '"', '<', '>', '&'
0461: *
0462: * @param d The device to print out on
0463: * @param s the String to print
0464: * @param quoteNewline should newlines be transformed into <code><br></code> tags
0465: * @param quoteSpaces should spaces be transformed into <code>&nbsp</code> chars
0466: * @param quoteApostroph Quote apostroph <code>'</code> by <code>\'</code>
0467: * @throws IOException
0468: */
0469: public static void quote(final Device d, final String s,
0470: final boolean quoteNewline, final boolean quoteSpaces,
0471: final boolean quoteApostroph) throws IOException {
0472: if (s == null) {
0473: return;
0474: }
0475: char[] chars = s.toCharArray();
0476: char c;
0477: int last = 0;
0478: for (int pos = 0; pos < chars.length; ++pos) {
0479: c = chars[pos];
0480: // write special characters as code ..
0481: if (c < 32 || c > 127) {
0482: d.print(chars, last, (pos - last));
0483: if (c == '\n' && quoteNewline) {
0484: d.print("<br>");
0485: if (pos == chars.length - 1
0486: || chars[pos + 1] == '\n') // insert in empty/sequence <br>s.
0487: d.print(" ");
0488: } else {
0489: d.print("&#");
0490: d.print((int) c);
0491: d.print(';');
0492: } // end of if ()
0493: last = pos + 1;
0494: } else {
0495: switch (c) {
0496: case '&':
0497: d.print(chars, last, (pos - last));
0498: d.print("&");
0499: last = pos + 1;
0500: break;
0501: case '"':
0502: d.print(chars, last, (pos - last));
0503: d.print(""");
0504: last = pos + 1;
0505: break;
0506: case '<':
0507: d.print(chars, last, (pos - last));
0508: d.print("<");
0509: last = pos + 1;
0510: break;
0511: case '>':
0512: d.print(chars, last, (pos - last));
0513: d.print(">");
0514: last = pos + 1;
0515: break;
0516: /*
0517: * watchout: we cannot replace _space_ by
0518: * since non-breakable-space is a different
0519: * character: isolatin-char 160, not 32.
0520: * This will result in a confusion in forms:
0521: * - the user enters space, presses submit
0522: * - the form content is written to the Device by wingS,
0523: * space is replaced by
0524: * - the next time the form is submitted, we get
0525: * isolatin-char 160, _not_ space.
0526: * (at least Konqueror behaves this correct; mozilla does not)
0527: * Henner
0528: *
0529: * But we must do this for IE, since it doesn't accept the
0530: * white-space: pre; property...so conditionalize it.
0531: * Ole
0532: */
0533: case ' ':
0534: if (quoteSpaces) {
0535: d.print(chars, last, (pos - last));
0536: d.print(" ");
0537: last = pos + 1;
0538: }
0539: break;
0540: /* Needed for i.e. js-code. */
0541: case '\'':
0542: if (quoteApostroph) {
0543: d.print(chars, last, (pos - last));
0544: d.print('\\');
0545: last = pos;
0546: }
0547: break;
0548:
0549: }
0550: }
0551: }
0552: d.print(chars, last, chars.length - last);
0553: }
0554:
0555: /**
0556: * Writes text to the device without any HTML tag content.
0557: * @param device The output device to use for quoting
0558: * @param htmlWrappedText The text which may contain HTML to strip.
0559: * @return The amount of characters written to the ouput device
0560: * @throws IOException
0561: */
0562: public static int writeWithoutHTML(final Device device,
0563: final String htmlWrappedText) throws IOException {
0564: final char[] chars = htmlWrappedText.toCharArray();
0565: int pos = 0;
0566: int len = 0;
0567: for (int c = 0; c < chars.length; c++) {
0568: switch (chars[c]) {
0569: case '\n':
0570: chars[c] = ' ';
0571: break;
0572: case '<':
0573: len += (c - pos);
0574: device.print(chars, pos, c - pos);
0575: break;
0576: case '>':
0577: pos = c + 1;
0578: }
0579: }
0580: final int remain = chars.length - pos;
0581: device.print(chars, pos, remain);
0582: len += remain;
0583: return len;
0584: }
0585:
0586: /**
0587: * write string as it is
0588: *
0589: * @param d
0590: * @param s
0591: * @throws IOException
0592: */
0593: public static void writeRaw(Device d, String s) throws IOException {
0594: if (s == null) {
0595: return;
0596: }
0597: d.print(s);
0598: }
0599:
0600: /**
0601: * writes the given String to the device. The string is quoted, i.e.
0602: * for all special characters in *ML, their appropriate entity is
0603: * returned.
0604: * If the String starts with '<html>', the content is regarded being
0605: * HTML-code and is written as is (without the <html> tag).
0606: */
0607: public static void write(Device d, String s) throws IOException {
0608: writeQuoted(d, s, false);
0609: }
0610:
0611: /**
0612: * writes the given String to the device. The string is quoted, i.e.
0613: * for all special characters in *ML, their appropriate entity is
0614: * returned.
0615: * If the String starts with '<html>', the content is regarded being
0616: * HTML-code and is written as is (without the <html> tag).
0617: * It is possible to define the quoteNewline behavoiur
0618: */
0619: public static void writeQuoted(Device d, String s,
0620: boolean quoteNewline) throws IOException {
0621: if (s == null) {
0622: return;
0623: }
0624: if ((s.length() > 5)
0625: && (s.substring(0, 6).equalsIgnoreCase("<html>"))) {
0626: writeRaw(d, s.substring(6));
0627: } else {
0628: quote(d, s, quoteNewline, false, false);
0629: }
0630: }
0631:
0632: /**
0633: * Prints an <b>optional</b> attribute. If the String value has a content
0634: * (value != null && value.length > 0), the attrib is added otherwise
0635: * it is left out
0636: */
0637: public static void optAttribute(final Device d, String attr,
0638: final SStringBuilder value) throws IOException {
0639: optAttribute(d, attr, value != null ? value.toString() : null);
0640: }
0641:
0642: /**
0643: * Prints an <b>optional</b> attribute. If the String value has a content
0644: * (value != null && value.length > 0), the attrib is added otherwise
0645: * it is left out
0646: */
0647: public static void optAttribute(final Device d,
0648: final char[] attributeName, final String value)
0649: throws IOException {
0650: if (value != null && value.length() > 0) {
0651: d.print(' ').print(attributeName).print("=\"");
0652: quote(d, value, true, false, false);
0653: d.print('"');
0654: }
0655: }
0656:
0657: /**
0658: * Prints an <b>optional</b> attribute. If the String value has a content
0659: * (value != null && value.length > 0), the attrib is added otherwise
0660: * it is left out
0661: */
0662: public static void optAttribute(final Device d,
0663: final String attributeName, final String value)
0664: throws IOException {
0665: if (value != null && value.length() > 0) {
0666: d.print(' ').print(attributeName).print("=\"");
0667: quote(d, value, true, false, false);
0668: d.print('"');
0669: }
0670: }
0671:
0672: /**
0673: * Prints an <b>mandatory</b> attribute. If the String value has a content
0674: * (value != null && value.length > 0), the attrib is added otherwise
0675: * it is left out
0676: */
0677: public static void attribute(final Device d, final String attr,
0678: final String value) throws IOException {
0679: d.print(' ').print(attr).print("=\"");
0680: if (value != null)
0681: quote(d, value, true, false, false);
0682: d.print('"');
0683: }
0684:
0685: /**
0686: * Prints an <b>optional</b> attribute. If the String value has a content
0687: * (value != null && value.length > 0), the attrib is added otherwise
0688: * it is left out
0689: */
0690: public static void optAttribute(final Device d, final String attr,
0691: final Color value) throws IOException {
0692: if (value != null) {
0693: d.print(' ');
0694: d.print(attr);
0695: d.print("=\"");
0696: write(d, value);
0697: d.print('"');
0698: }
0699: }
0700:
0701: /**
0702: * Prints an optional, renderable attribute.
0703: */
0704: public static void optAttribute(final Device d, final String attr,
0705: final Renderable r) throws IOException {
0706: if (r != null) {
0707: d.print(' ');
0708: d.print(attr);
0709: d.print("=\"");
0710: r.write(d);
0711: d.print('"');
0712: }
0713: }
0714:
0715: /**
0716: * Prints an <b>optional</b> attribute. If the integer value is greater than 0,
0717: * the attrib is added otherwise it is left out
0718: */
0719: public static void optAttribute(final Device d,
0720: final char[] attributeName, final int value)
0721: throws IOException {
0722: if (value > 0) {
0723: d.print(' ');
0724: d.print(attributeName);
0725: d.print("=\"");
0726: d.print(String.valueOf(value));
0727: d.print('"');
0728: }
0729: }
0730:
0731: /**
0732: * Prints an <b>optional</b> attribute. If the integer value is greater than 0,
0733: * the attrib is added otherwise it is left out
0734: */
0735: public static void optAttribute(final Device d,
0736: final String attributeName, final int value)
0737: throws IOException {
0738: if (value > 0) {
0739: d.print(' ');
0740: d.print(attributeName);
0741: d.print("=\"");
0742: d.print(String.valueOf(value));
0743: d.print('"');
0744: }
0745: }
0746:
0747: /**
0748: * Prints an <b>optional</b> attribute. If the dimension value not equals <i>null</i>
0749: * the attrib is added otherwise it is left out
0750: */
0751: public static void optAttribute(final Device d, final String attr,
0752: final SDimension value) throws IOException {
0753: if (value != null) {
0754: d.print(' ');
0755: d.print(attr);
0756: d.print("=\"");
0757: write(d, value.toString());
0758: d.print('"');
0759: }
0760: }
0761:
0762: /**
0763: * Prints all <b>optional</b> attributes that are contained in the
0764: * <code>Map</code>. The keys of the map should be instances
0765: * of <code>String</code> and the values one of the following
0766: * classes.<br/>
0767: * <ul>
0768: * <li>org.wings.util.SStringBuilder</li>
0769: * <li>java.lang.String</li>
0770: * <li>java.awt.Color</li>
0771: * <li>org.wings.Renderable</li>
0772: * <li>java.lang.Integer</li>
0773: * <li>org.wings.SDimension</li>
0774: * </ul>
0775: *
0776: * @param d The device to print the <b>optional</b> attributes.
0777: * @param attributes The <b>optional</b> attributes. The key is the attribute
0778: * name and the value is the attribute value.
0779: * @throws IOException The exception maybe thrown if an error occurs
0780: * while trying to write to device.
0781: */
0782: public static void optAttributes(final Device d,
0783: final Map attributes) throws IOException {
0784: if (attributes != null) {
0785: for (final Object o : attributes.entrySet()) {
0786: Map.Entry entries = (Map.Entry) o;
0787:
0788: Object key = entries.getKey();
0789: if (key instanceof String) {
0790: String attr = (String) key;
0791:
0792: Object value = entries.getValue();
0793: if (value instanceof SStringBuilder) {
0794: Utils.optAttribute(d, attr,
0795: (SStringBuilder) value);
0796: } else if (value instanceof String) {
0797: Utils.optAttribute(d, attr, (String) value);
0798: } else if (value instanceof Color) {
0799: Utils.optAttribute(d, attr, (Color) value);
0800: } else if (value instanceof Renderable) {
0801: Utils.optAttribute(d, attr, (Renderable) value);
0802: } else if (value instanceof Integer) {
0803: Utils.optAttribute(d, attr, ((Integer) value)
0804: .intValue());
0805: } else if (value instanceof SDimension) {
0806: Utils.optAttribute(d, attr, (SDimension) value);
0807: }
0808: }
0809: }
0810: }
0811: }
0812:
0813: /**
0814: * writes the given java.awt.Color to the device. Speed optimized;
0815: * character conversion avoided.
0816: */
0817: public static void write(final Device d, final Color c)
0818: throws IOException {
0819: d.print('#');
0820: int rgb = (c == null) ? 0 : c.getRGB();
0821: int mask = 0xf00000;
0822: for (int bitPos = 20; bitPos >= 0; bitPos -= 4) {
0823: d.print(hexDigits[(rgb & mask) >>> bitPos]);
0824: mask >>>= 4;
0825: }
0826: }
0827:
0828: /**
0829: * writes anything Renderable
0830: */
0831: public static void write(final Device d, final Renderable r)
0832: throws IOException {
0833: if (r == null) {
0834: return;
0835: }
0836: r.write(d);
0837: }
0838:
0839: /*
0840: * testing purposes.
0841: */
0842: public static void main(String argv[]) throws Exception {
0843: Color c = new Color(255, 254, 7);
0844: Device d = new org.wings.io.StringBuilderDevice(1024);
0845: write(d, c);
0846: quote(d, "\nThis is a <abc> string \"; foo & sons\nmoin", true,
0847: false, false);
0848: d.print(String.valueOf(-42));
0849: d.print(String.valueOf(Integer.MIN_VALUE));
0850:
0851: write(d, "hello test \n");
0852: write(d, "<html>hallo test \n");
0853:
0854: d = new org.wings.io.NullDevice();
0855: long start = System.currentTimeMillis();
0856: for (int i = 0; i < 1000000; ++i) {
0857: quote(d, "this is a little & foo", true, false, false);
0858: }
0859: System.out.println("took: "
0860: + (System.currentTimeMillis() - start) + "ms");
0861: }
0862:
0863: /**
0864: * Helper method for CGs to print out debug information in output stream.
0865: * If {@link #PRINT_DEBUG} prints the passed string and returns the current {@link Device}.
0866: * In other case omits ouput and returns a {@link NullDevice}
0867: *
0868: * @param d The original device
0869: * @return The original device or a {@link NullDevice}
0870: */
0871: public static Device printDebug(final Device d, final char[] s)
0872: throws IOException {
0873: if (PRINT_DEBUG) {
0874: return d.print(s);
0875: } else {
0876: return d;
0877: }
0878: }
0879:
0880: /**
0881: * Helper method for CGs to print out debug information in output stream.
0882: * If {@link #PRINT_DEBUG} prints the passed string and returns the current {@link Device}.
0883: * In other case omits ouput and returns a {@link NullDevice}
0884: *
0885: * @param d The original device
0886: * @return The original device or a {@link NullDevice}
0887: */
0888: public static Device printDebug(final Device d, final String s)
0889: throws IOException {
0890: if (PRINT_DEBUG) {
0891: return d.print(s);
0892: } else {
0893: return d;
0894: }
0895: }
0896:
0897: /**
0898: * Prints a hierarchical idented newline if debug mode is enabled.
0899: * {@link #printNewline(org.wings.io.Device, org.wings.SComponent)}
0900: */
0901: public static Device printDebugNewline(Device d,
0902: SComponent currentComponent) throws IOException {
0903: if (PRINT_DEBUG) {
0904: return printNewline(d, currentComponent);
0905: } else {
0906: return NullDevice.DEFAULT;
0907: }
0908: }
0909:
0910: /**
0911: * Prints a hierarchical idented newline. For each surrounding container of the passed component one ident level.
0912: */
0913: public static Device printNewline(final Device d,
0914: SComponent currentComponent) throws IOException {
0915: // special we save every ms handling for holger ;-)
0916: /* (OL) I took out the test for PRINT_DEBUG, since
0917: * sometimes we just need newlines (example tabbedPane)
0918: * I hope Holger doesn't need that microsecond ;)
0919: */
0920: if (currentComponent == null) {
0921: return d;
0922: }
0923: d.print('\n');
0924:
0925: if (PRINT_PRETTY) {
0926: SContainer current = currentComponent.getParent();
0927: while (current != null) {
0928: d.print('\t');
0929: current = current.getParent();
0930: }
0931: }
0932: return d;
0933: }
0934:
0935: /**
0936: * Prints a hierarchical idented newline. For each surrounding container of the passed component one ident level.
0937: */
0938: public static Device printNewline(final Device d,
0939: SComponent currentComponent, int offset) throws IOException {
0940: d.print('\n');
0941: if (PRINT_PRETTY) {
0942: while (currentComponent != null) {
0943: d.print('\t');
0944: currentComponent = currentComponent.getParent();
0945: }
0946: }
0947:
0948: while (offset > 0) {
0949: d.print('\t');
0950: offset--;
0951: }
0952: return d;
0953: }
0954:
0955: /**
0956: * loads a script from disk through the classloader.
0957: *
0958: * @param path the path where the script can be found
0959: * @return the script as a String
0960: */
0961: public static String loadScript(final String path) {
0962: InputStream in = null;
0963: BufferedReader reader = null;
0964:
0965: try {
0966: in = Thread.currentThread().getContextClassLoader()
0967: .getResourceAsStream(path);
0968: reader = new BufferedReader(new InputStreamReader(in));
0969: SStringBuilder buffer = new SStringBuilder();
0970: String line;
0971: while ((line = reader.readLine()) != null) {
0972: buffer.append(line).append("\n");
0973: }
0974: buffer.append("\n");
0975:
0976: return buffer.toString();
0977: } catch (Exception e) {
0978: log.warn("Unable to load script '" + path + "'", e);
0979: return "";
0980: } finally {
0981: try {
0982: in.close();
0983: } catch (Exception ign) {
0984: }
0985: try {
0986: reader.close();
0987: } catch (Exception ign1) {
0988: }
0989: }
0990: }
0991:
0992: /**
0993: * prints a String. Substitutes spaces with nbsp's
0994: */
0995: public static String nonBreakingSpaces(String text) {
0996: return text.replace(' ', '\u00A0');
0997: }
0998:
0999: /**
1000: * Takes a string, tokenizes it and appends the wordSuffix on each word.
1001: *
1002: * @param words A list of words, may be <code>null</code>.
1003: * @param wordSuffix A suffix string to append to each word
1004: * @return modified string (<code>i.e. "slayout myclass","_box"</code> gets <code>"slayout_box myclass_box"</code>).
1005: */
1006: public static String appendSuffixesToWords(String words,
1007: String wordSuffix) {
1008: if (words == null || words.length() == 0 || wordSuffix == null
1009: || wordSuffix.length() == 0) {
1010: return words;
1011: }
1012:
1013: // trivial case
1014: if (words.indexOf(' ') < 0) {
1015: return words + wordSuffix;
1016: }
1017:
1018: // more than one word
1019: StringTokenizer tokenizer = new StringTokenizer(words, " ");
1020: SStringBuilder returnValue = new SStringBuilder();
1021: while (tokenizer.hasMoreElements()) {
1022: returnValue.append(tokenizer.nextToken())
1023: .append(wordSuffix);
1024: if (tokenizer.hasMoreTokens()) {
1025: returnValue.append(' ');
1026: }
1027: }
1028:
1029: return returnValue.toString();
1030: }
1031:
1032: /**
1033: * Prepends the component style class set on the component to the existing style string.
1034: *
1035: * @param component Component may be <code>null</code> and may have a <code>null</code> style string.
1036: * @param styleString The style string to append
1037: */
1038: public static SStringBuilder joinStyles(final SComponent component,
1039: final SStringBuilder styleString) {
1040: if (component != null && component.getStyle() != null) {
1041: if (styleString != null) {
1042: styleString.insert(0, " ");
1043: styleString.insert(0, component.getStyle());
1044: return styleString;
1045: } else {
1046: return new SStringBuilder(component.getStyle());
1047: }
1048: } else {
1049: return styleString;
1050: }
1051: }
1052:
1053: /**
1054: * Prepends the component style class set on the component to the existing style string.
1055: *
1056: * @param component Component may be <code>null</code> and may have a <code>null</code> style string.
1057: * @param styleString The style string to append
1058: */
1059: public static String joinStyles(final SComponent component,
1060: final String styleString) {
1061: if (component != null && component.getStyle() != null) {
1062: if (styleString != null) {
1063: return component.getStyle() + " " + styleString;
1064: } else {
1065: return component.getStyle();
1066: }
1067: } else {
1068: return styleString != null ? styleString : "";
1069: }
1070: }
1071:
1072: public static void printButtonStart(Device device,
1073: SComponent eventTarget, String eventValue, boolean b,
1074: boolean showAsFormComponent) throws IOException {
1075: printButtonStart(device, eventTarget, eventValue, b,
1076: showAsFormComponent, null);
1077: }
1078:
1079: public static void printButtonStart(final Device device,
1080: final SComponent component, final String eventValue,
1081: final boolean enabled, final boolean formComponent,
1082: String cssClassName) throws IOException {
1083: if (enabled) {
1084: device.print("<a href=\"#\"");
1085: printClickability(device, component, eventValue, enabled,
1086: formComponent);
1087: } else {
1088: device.print("<span");
1089: }
1090:
1091: Utils.optAttribute(device, "class", cssClassName);
1092: }
1093:
1094: public static void printButtonEnd(final Device device,
1095: final boolean enabled) throws IOException {
1096: if (enabled)
1097: device.print("</a>");
1098: else
1099: device.print("</span>");
1100: }
1101:
1102: public static void printClickability(final Device device,
1103: final SComponent component, final String eventValue,
1104: final boolean enabled, final boolean formComponent)
1105: throws IOException {
1106: if (enabled) {
1107: // Render onclick JS listeners
1108: device.print(" onclick=\"wingS.request.sendEvent(event,");
1109: device.print(formComponent);
1110: device.print(',');
1111: device.print(!component.isReloadForced());
1112: device.print(",'");
1113: device.print(Utils.event(component));
1114: device.print("','");
1115: if (eventValue != null) {
1116: device.print(eventValue);
1117: }
1118: device.print('\'');
1119: device.print(collectJavaScriptListenerCode(component,
1120: JavaScriptEvent.ON_CLICK));
1121: device.print("); return false;\"");
1122:
1123: // Render remaining JS listeners
1124: Utils.writeEvents(device, component, EXCLUDE_ON_CLICK);
1125: }
1126: }
1127:
1128: /**
1129: * Renders inline the javascript code attached to the passed javascipt event type
1130: * on the component. Used to allow usage of javascript events by the framework
1131: * as well as by the application itself.
1132: * <p> For an example: See the <code>wingS.request.sendEvent()</code>.
1133: *
1134: * @param component The component wearing the event handler
1135: * @param javascriptEventType the event type declared in {@link JavaScriptEvent}
1136: * @return javascript code fragment n the form of <code>,new Array(function(){...},function(){...})</code>
1137: */
1138: public static String collectJavaScriptListenerCode(
1139: final SComponent component, final String javascriptEventType) {
1140: SStringBuilder script = null;
1141: JavaScriptListener[] eventListeners = getEventTypeListeners(
1142: component, javascriptEventType);
1143: if (eventListeners != null && eventListeners.length > 0) {
1144: for (int i = 0; i < eventListeners.length; ++i) {
1145: if (eventListeners[i].getCode() != null) {
1146: if (script == null) {
1147: script = new SStringBuilder(64);
1148: }
1149: if (i > 0) {
1150: script.append(",");
1151: }
1152: script.append("function(){").append(
1153: eventListeners[i].getCode()).append("}");
1154: }
1155: }
1156: if (script != null && script.length() > 0) {
1157: script.insert(0, ",new Array(");
1158: script.append(")");
1159: }
1160: }
1161: return script != null ? script.toString() : "";
1162: }
1163:
1164: /**
1165: * @param button The component wearing the event handler
1166: * @param javaScriptEvent the event type declared in {@link JavaScriptEvent}
1167: * @return The attached listeners to event type
1168: */
1169: private static JavaScriptListener[] getEventTypeListeners(
1170: final SComponent button, final String javaScriptEvent) {
1171: ArrayList<JavaScriptListener> result = new ArrayList<JavaScriptListener>();
1172: ScriptListener[] listeners = button.getScriptListeners();
1173: for (ScriptListener listener : listeners) {
1174: if (listener instanceof JavaScriptListener) {
1175: JavaScriptListener jsListener = (JavaScriptListener) listener;
1176: if (javaScriptEvent.equals(jsListener.getEvent()
1177: .toLowerCase())) {
1178: result.add(jsListener);
1179: }
1180: }
1181: }
1182: return result.toArray(new JavaScriptListener[result.size()]);
1183: }
1184:
1185: public static SStringBuilder inlineStyles(Style tabAreaStyle) {
1186: if (tabAreaStyle != null) {
1187: SStringBuilder tabArea = new SStringBuilder();
1188: tabArea.append(tabAreaStyle.toString());
1189: return tabArea;
1190: } else {
1191: return null;
1192: }
1193: }
1194:
1195: /**
1196: * @return true if current browser is microsoft exploder
1197: */
1198: public static boolean isMSIE(SComponent component) {
1199: return component.getSession().getUserAgent().getBrowserType() == BrowserType.IE;
1200: }
1201:
1202: /**
1203: * @return true if current browser is microsoft exploder
1204: */
1205: public static boolean isMSIE() {
1206: return SessionManager.getSession().getUserAgent()
1207: .getBrowserType() == BrowserType.IE;
1208: }
1209:
1210: /**
1211: * @param insets The inset param to test
1212: * @return <code>true</code> if any valid inset greater zero is set
1213: */
1214: public static boolean hasInsets(Insets insets) {
1215: return insets != null
1216: && (insets.top > 0 || insets.left > 0
1217: || insets.right > 0 || insets.bottom > 0);
1218: }
1219:
1220: public static void optFullSize(Device device, SComponent component)
1221: throws IOException {
1222: SDimension dim = component.getPreferredSize();
1223: if (dim != null) {
1224: String width = dim.getWidth();
1225: boolean widthSet = width != null && !"".equals(width)
1226: && !SDimension.AUTO.equals(width);
1227: String height = dim.getHeight();
1228: boolean heightSet = height != null && !"".equals(height)
1229: && !SDimension.AUTO.equals(height);
1230: SStringBuilder style = new SStringBuilder();
1231: if (widthSet) {
1232: style.append("width:100%;");
1233: }
1234: if (heightSet) {
1235: style.append("height:100%;");
1236: }
1237: if (style.length() > 0)
1238: Utils.optAttribute(device, "style", style);
1239: }
1240: }
1241:
1242: /**
1243: * Converts a hgap/vgap in according inline css padding style.
1244: *
1245: * @param insets The insets to generate CSS padding declaration
1246: * @return Empty or filled stringbuffer with padding declaration
1247: */
1248: public static SStringBuilder createInlineStylesForInsets(
1249: Insets insets) {
1250: return createInlineStylesForInsets(new SStringBuilder(), insets);
1251: }
1252:
1253: /**
1254: * Converts a hgap/vgap in according inline css padding style.
1255: *
1256: * @param device Appender to append inset style.
1257: * @param insets The insets to generate CSS padding declaration
1258: * @return Empty or filled stringbuffer with padding declaration
1259: */
1260: public static Device printInlineStylesForInsets(
1261: final Device device, final Insets insets)
1262: throws IOException {
1263: if (insets != null
1264: && (insets.top > 0 || insets.left > 0
1265: || insets.right > 0 || insets.bottom > 0)) {
1266: if (insets.top == insets.left
1267: && insets.left == insets.right
1268: && insets.right == insets.bottom) {
1269: device.print(" style=\"padding:").print(insets.top)
1270: .print("px;\"");
1271: } else if (insets.top == insets.bottom
1272: && insets.left == insets.right) {
1273: device.print(" style=\"padding:").print(insets.top)
1274: .print("px ").print(insets.right)
1275: .print("px;\"");
1276: } else {
1277: device.print(" style=\"padding:").print(insets.top)
1278: .print("px ").print(insets.right).print("px ")
1279: .print(insets.bottom).print("px ").print(
1280: insets.left).print("px;\"");
1281: }
1282: }
1283: return device;
1284: }
1285:
1286: /**
1287: * Converts a hgap/vgap in according inline css padding style.
1288: *
1289: * @param styles Appender to append inset style.
1290: * @param insets The insets to generate CSS padding declaration
1291: * @return Empty or filled stringbuffer with padding declaration
1292: */
1293: public static SStringBuilder createInlineStylesForInsets(
1294: SStringBuilder styles, Insets insets) {
1295: if (insets != null
1296: && (insets.top > 0 || insets.left > 0
1297: || insets.right > 0 || insets.bottom > 0)) {
1298: if (insets.top == insets.left
1299: && insets.left == insets.right
1300: && insets.right == insets.bottom) {
1301: styles.append("padding:").append(insets.top).append(
1302: "px;");
1303: } else if (insets.top == insets.bottom
1304: && insets.left == insets.right) {
1305: styles.append("padding:").append(insets.top).append(
1306: "px ").append(insets.right).append("px;");
1307: } else {
1308: styles.append("padding:").append(insets.top).append(
1309: "px ").append(insets.right).append("px ")
1310: .append(insets.bottom).append("px ").append(
1311: insets.left).append("px;");
1312: }
1313: }
1314: return styles;
1315: }
1316:
1317: public static int calculateHorizontalOversize(SComponent component,
1318: boolean percentageUnitOnly) {
1319: if (component != null && isMSIE(component)
1320: && component instanceof STextComponent) {
1321: SDimension preferredSize = component.getPreferredSize();
1322: if (preferredSize != null) {
1323: String widthUnit = preferredSize.getWidthUnit();
1324: if (!SDimension.AUTO.equals(widthUnit)) {
1325: if (percentageUnitOnly && !"%".equals(widthUnit))
1326: return 0;
1327:
1328: SAbstractBorder border = (SAbstractBorder) component
1329: .getBorder();
1330: if (border != SDefaultBorder.INSTANCE) {
1331: int oversize = 0;
1332: int thickness = border
1333: .getThickness(SConstants.LEFT);
1334: if (thickness != -1)
1335: oversize += thickness;
1336: thickness = border
1337: .getThickness(SConstants.RIGHT);
1338: if (thickness != -1)
1339: oversize += thickness;
1340: final Insets insets = border.getInsets();
1341: if (insets != null) {
1342: oversize += insets.left + insets.right;
1343: }
1344: return oversize;
1345: } else {
1346: return ((Integer) component
1347: .getClientProperty("horizontalOversize"))
1348: .intValue();
1349: }
1350: }
1351: }
1352: }
1353: return 0;
1354: }
1355:
1356: public static int calculateVerticalOversize(SComponent component,
1357: boolean percentageUnitOnly) {
1358: if (component != null && isMSIE(component)
1359: && component instanceof STextComponent) {
1360: SDimension preferredSize = component.getPreferredSize();
1361: if (preferredSize != null) {
1362: String heightUnit = preferredSize.getHeightUnit();
1363: if (!SDimension.AUTO.equals(heightUnit)) {
1364: if (percentageUnitOnly && !"%".equals(heightUnit))
1365: return 0;
1366:
1367: SAbstractBorder border = (SAbstractBorder) component
1368: .getBorder();
1369: if (border != SDefaultBorder.INSTANCE) {
1370: int oversize = 0;
1371: int thickness = border
1372: .getThickness(SConstants.TOP);
1373: if (thickness != -1)
1374: oversize += thickness;
1375: thickness = border
1376: .getThickness(SConstants.BOTTOM);
1377: if (thickness != -1)
1378: oversize += thickness;
1379: final Insets insets = border.getInsets();
1380: if (insets != null) {
1381: oversize += insets.top + insets.bottom;
1382: }
1383: return oversize;
1384: } else {
1385: return 4;
1386: }
1387: }
1388: }
1389: }
1390: return 0;
1391: }
1392:
1393: /**
1394: * Lookup keys for wings resources
1395: */
1396: public static final String JS_WINGS_ALL = "JS.wingsAll";
1397: public static final String JS_WINGS_ALL_DEBUG = "JS.wingsAllDebug";
1398:
1399: /**
1400: * Lookup keys for yui resources
1401: */
1402: public static final String JS_YUI_ANIMATION = "JS.yuiAnimation";
1403: // CSS: public static final String CSS_YUI_ASSETS_... = "CSS.yuiAssets...";
1404: public static final String CSS_YUI_ASSETS_CALENDAR = "CSS.yuiAssetsCalendar";
1405: public static final String CSS_YUI_ASSETS_CONTAINER = "CSS.yuiAssetsContainer";
1406: public static final String IMG_YUI_ASSETS_SPRITE = "IMG.yuiAssetsSprite";
1407: public static final String JS_YUI_AUTOCOMPLETE = "JS.yuiAutocomplete";
1408: // CSS: public static final String CSS_YUI_BASE = "CSS.yuiBase";
1409: public static final String JS_YUI_BUTTON = "JS.yuiButton";
1410: public static final String JS_YUI_CALENDAR = "JS.yuiCalendar";
1411: // Experimental: public static final String JS_YUI_CHARTS = "JS.yuiCharts";
1412: public static final String JS_YUI_COLORPICKER = "JS.yuiColorpicker";
1413: public static final String JS_YUI_CONNECTION = "JS.yuiConnection";
1414: public static final String JS_YUI_CONTAINER = "JS.yuiContainer";
1415: public static final String JS_YUI_CONTAINER_CORE = "JS.yuiContainerCore";
1416: // Beta: public static final String JS_YUI_COOKIE = "JS.yuiCookie";
1417: // Beta: public static final String JS_YUI_DATASOURCE = "JS.yuiDatasource";
1418: // Beta: public static final String JS_YUI_DATATABLE = "JS.yuiDatatable";
1419: public static final String JS_YUI_DOM = "JS.yuiDom";
1420: public static final String JS_YUI_DRAGDROP = "JS.yuiDragdrop";
1421: // Beta: public static final String JS_YUI_EDITOR = "JS.yuiEditor";
1422: // Beta: public static final String JS_YUI_EDITOR_SIMPLE = "JS.yuiEditorSimple";
1423: // Beta: public static final String JS_YUI_ELEMENT = "JS.yuiElement";
1424: public static final String JS_YUI_EVENT = "JS.yuiEvent";
1425: // CSS: public static final String CSS_YUI_FONTS = "CSS.yuiFonts";
1426: public static final String JS_YUI_GET = "JS.yuiGet";
1427: // CSS: public static final String CSS_YUI_GRIDS = "CSS.yuiGrids";
1428: public static final String JS_YUI_HISTORY = "JS.yuiHistory";
1429: // Beta: public static final String JS_YUI_IMAGECROPPER = "JS.yuiImagecropper";
1430: public static final String JS_YUI_IMAGELOADER = "JS.yuiImageloader";
1431: public static final String JS_YUI_JSON = "JS.yuiJson";
1432: // Beta: public static final String JS_YUI_LAYOUT = "JS.yuiLayout";
1433: public static final String JS_YUI_LOGGER = "JS.yuiLogger";
1434: public static final String JS_YUI_MENU = "JS.yuiMenu";
1435: // Beta: public static final String JS_YUI_PROFILER = "JS.yuiProfiler";
1436: // Beta: public static final String JS_YUI_PROFILERVIEWER = "JS.yuiProfilerviewer";
1437: // CSS: public static final String CSS_YUI_RESET = "CSS.yuiReset";
1438: // CSS: public static final String CSS_YUI_RESET_FONTS = "CSS.yuiResetFonts";
1439: // CSS: public static final String CSS_YUI_RESET_FONTS_GRIDS = "CSS.yuiResetFontsGrids";
1440: // Beta: public static final String JS_YUI_RESIZE = "JS.yuiResize";
1441: // Beta: public static final String JS_YUI_SELECTOR = "JS.yuiSelector";
1442: public static final String JS_YUI_SLIDER = "JS.yuiSlider";
1443: public static final String JS_YUI_TABVIEW = "JS.yuiTabview";
1444: public static final String JS_YUI_TREEVIEW = "JS.yuiTreeview";
1445: // Experimental: public static final String JS_YUI_UPLOADER = "JS.yuiUploader";
1446: public static final String JS_YUI_UTILITIES = "JS.yuiUtilities";
1447: public static final String JS_YUI_YAHOO = "JS.yuiYahoo";
1448: public static final String JS_YUI_YAHOO_DOM_EVENT = "JS.yuiYahooDomEvent";
1449: // Beta: public static final String JS_YUI_YUILOADER = "JS.yuiYuiloader";
1450: public static final String JS_YUI_YUILOADER_DOM_EVENT = "JS.yuiYuiloaderDomEvent";
1451: public static final String JS_YUI_TEST = "JS.yuiYuitest";
1452: public static final String JS_YUI_TEST_CORE = "JS.yuiYuitestCore";
1453:
1454: public static final String JS_YUI_ANIMATION_DEBUG = "JS.yuiAnimationDebug";
1455: public static final String JS_YUI_CONNECTION_DEBUG = "JS.yuiConnectionDebug";
1456: public static final String JS_YUI_CONTAINER_DEBUG = "JS.yuiContainerDebug";
1457: public static final String JS_YUI_DOM_DEBUG = "JS.yuiDomDebug";
1458: public static final String JS_YUI_DRAGDROP_DEBUG = "JS.yuiDragdropDebug";
1459: public static final String JS_YUI_EVENT_DEBUG = "JS.yuiEventDebug";
1460: public static final String JS_YUI_ELEMENT_DEBUG = "JS.yuiElementDebug";
1461: public static final String JS_YUI_YAHOO_DEBUG = "JS.yuiYahooDebug";
1462:
1463: /**
1464: * Lookup keys for other resources
1465: */
1466: public static final String JS_ETC_MENU = "JS.etcMenu";
1467: public static final String JS_ETC_POPUP = "JS.etcPopup";
1468: public static final String JS_ETC_WZ_TOOLTIP = "JS.etcWzTooltip";
1469:
1470: public static final String JS_DEBUG_FIREBUGLITE = "JS.debugFirebugLite";
1471: public static final String HTML_DEBUG_FIREBUGLITE = "HTML.debugFirebugLite";
1472: public static final String CSS_DEBUG_FIREBUGLITE = "CSS.debugFirebugLite";
1473: public static final String IMG_DEBUG_FIREBUGLITE_ERROR = "IMG.debugFirebugLiteError";
1474: public static final String IMG_DEBUG_FIREBUGLITE_WARN = "IMG.debugFirebugLiteWarn";
1475: public static final String IMG_DEBUG_FIREBUGLITE_INFO = "IMG.debugFirebugLiteInfo";
1476:
1477: /**
1478: * Load a Javascript library that comes with wingS by a property. Check <code>JS_XXX</code> constants.
1479: * @param jsResourceProperty A property lookup key, preferably by a constant in this utility class
1480: * @return A script reference to the desired script addable as header
1481: */
1482: public static Script createExternalizedJSHeaderFromProperty(
1483: String jsResourceProperty) {
1484: String jsClassPath = (String) ResourceManager.getObject(
1485: jsResourceProperty, String.class);
1486: return createExternalizedJSHeader(jsClassPath);
1487: }
1488:
1489: /**
1490: * Load a Javascript library from the classpath.
1491: * @param jsClassPath A classpath to the .js-file
1492: * @return A script reference to the desired script addable as header
1493: */
1494: public static Script createExternalizedJSHeader(String jsClassPath) {
1495: ClassPathResource res = new ClassPathJavascriptResource(
1496: jsClassPath, HEADER_LOADED_CALLBACK);
1497: ExternalizeManager extMgr = SessionManager.getSession()
1498: .getExternalizeManager();
1499: String jsUrl = extMgr.externalize(res,
1500: ExternalizeManager.GLOBAL);
1501: return new JavaScriptHeader(new SessionResource(jsUrl));
1502: }
1503:
1504: /**
1505: * Load a Stylesheet document that comes with wingS by a property. Check <code>CSS_XXX</code> constants.
1506: * @param cssResourceProperty A property lookup key, preferably by a constant in this utility class
1507: * @return A Link reference to the desired stylesheet addable as header
1508: */
1509: public static Link createExternalizedCSSHeaderFromProperty(
1510: String cssResourceProperty) {
1511: String cssClassPath = (String) ResourceManager.getObject(
1512: cssResourceProperty, String.class);
1513: return createExternalizedCSSHeader(cssClassPath);
1514: }
1515:
1516: /**
1517: * Load a Stylesheet document from the classpath.
1518: * @param cssClassPath A classpath to the .css-file
1519: * @return A Link reference to the desired stylesheet addable as header
1520: */
1521: public static Link createExternalizedCSSHeader(String cssClassPath) {
1522: ClassPathResource res = new ClassPathResource(cssClassPath,
1523: "text/css");
1524: ExternalizeManager extMgr = SessionManager.getSession()
1525: .getExternalizeManager();
1526: String cssUrl = extMgr.externalize(res,
1527: ExternalizeManager.GLOBAL);
1528: return new StyleSheetHeader(new SessionResource(cssUrl));
1529: }
1530:
1531: public static Renderable listToJsArray(List list) {
1532: return new JSArray(list);
1533: }
1534:
1535: public static Renderable mapToJsObject(Map map) {
1536: return new JSObject(map);
1537: }
1538:
1539: private static final char[] BACKSLASH_N = "\\n".toCharArray();
1540: private static final char[] BACKSLASH_R = "\\r".toCharArray();
1541: private static final char[] BACKSLASH_T = "\\t".toCharArray();
1542:
1543: public static void encodeJS(Device d, Object o) throws IOException {
1544: // The common cases that never need escaping.
1545: if (o == null) {
1546: d.print("null");
1547: return;
1548: }
1549: if (o instanceof Number || o instanceof Boolean) {
1550: d.print(o.toString());
1551: return;
1552: }
1553: if (o instanceof Renderable) {
1554: ((Renderable) o).write(d);
1555: return;
1556: }
1557:
1558: // Using an Objects toString() method to JSON-serialize them is DIRTY.
1559: assert (o instanceof String) : " never rely on toString() to serialize "
1560: + "an object in JSON! Implement org.wings.Renderable instead. "
1561: + "Object was a " + o.getClass() + ": >" + o + "<";
1562:
1563: d.print('\'');
1564: final String stringRep = o.toString();
1565: final char chars[] = stringRep.toCharArray();
1566: char c;
1567: int last = 0;
1568: for (int pos = 0; pos < chars.length; ++pos) {
1569: c = chars[pos];
1570: switch (c) {
1571: case '\'':
1572: d.print(chars, last, (pos - last));
1573: d.print('\\');
1574: last = pos; // next starts with single quote
1575: break;
1576: case '\\':
1577: d.print(chars, last, (pos - last));
1578: d.print('\\');
1579: last = pos; // next starts with backslash
1580: break;
1581: case '\b':
1582: d.print(chars, last, (pos - last));
1583: d.print("\\b");
1584: last = pos + 1;
1585: break;
1586: case '\f':
1587: d.print(chars, last, (pos - last));
1588: d.print("\\f");
1589: last = pos + 1;
1590: break;
1591: case '\n':
1592: d.print(chars, last, (pos - last));
1593: d.print(BACKSLASH_N);
1594: last = pos + 1;
1595: break;
1596: case '\r':
1597: d.print(chars, last, (pos - last));
1598: d.print(BACKSLASH_R);
1599: last = pos + 1;
1600: break;
1601: case '\t':
1602: d.print(chars, last, (pos - last));
1603: d.print(BACKSLASH_T);
1604: last = pos + 1;
1605: break;
1606: case '/': // Regular expressions start with a slash.
1607: d.print(chars, last, (pos - last));
1608: d.print('\\');
1609: last = pos; // next starts with slash.
1610: break;
1611: default:
1612: if ((c >= '\u0000' && c <= '\u001F') || c >= '\u007F') {
1613: d.print(chars, last, pos - last);
1614: final String hex = Integer.toHexString(c);
1615: d.print("\\u");
1616: for (int j = 4 - hex.length(); j > 0; --j) {
1617: d.print('0');
1618: }
1619: d.print(hex);
1620: last = pos + 1;
1621: }
1622: }
1623: }
1624: d.print(chars, last, chars.length - last);
1625: d.print('\'');
1626: }
1627:
1628: public static void writeAllAttributes(Device device,
1629: SComponent component) throws IOException {
1630: optAttribute(device, "class", component.getStyle());
1631: optAttribute(device, "id", component.getName());
1632: optAttribute(device, "style", getInlineStyles(component));
1633:
1634: if (component instanceof LowLevelEventListener) {
1635: optAttribute(device, "eid", component.getLowLevelEventId());
1636: }
1637:
1638: // Tooltip handling
1639: writeTooltipMouseOver(device, component);
1640:
1641: // Component popup menu
1642: writeContextMenu(device, component);
1643: }
1644:
1645: public static String getInlineStyles(SComponent component) {
1646: // write inline styles
1647: final SStringBuilder builder = new SStringBuilder();
1648:
1649: appendCSSInlineSize(builder, component);
1650:
1651: // Determine Style String
1652: Style allStyle = component
1653: .getDynamicStyle(SComponent.SELECTOR_ALL);
1654: if (component instanceof SAbstractIconTextCompound
1655: && ((SAbstractIconTextCompound) component).isSelected()) {
1656: // present, SComponent.getDynamicStyle() always is instance of CSSStyle
1657: CSSStyle selectedStyle = (CSSStyle) component
1658: .getDynamicStyle(SAbstractIconTextCompound.SELECTOR_SELECTED);
1659: if (selectedStyle != null) {
1660: if (allStyle != null) {
1661: // make a copy to modify
1662: allStyle = new CSSStyle(SComponent.SELECTOR_ALL,
1663: (CSSAttributeSet) allStyle);
1664: allStyle.putAll(selectedStyle);
1665: } else {
1666: allStyle = selectedStyle;
1667: }
1668: }
1669: }
1670: // Render Style string
1671: if (allStyle != null)
1672: builder.append(allStyle.toString());
1673:
1674: final SBorder border = component.getBorder();
1675: if (border != null) {
1676: if (border.getAttributes() != null)
1677: builder.append(border.getAttributes().toString());
1678: } else
1679: builder.append("border:none;padding:0px");
1680:
1681: return builder.toString();
1682: }
1683:
1684: /**
1685: * Write JS code for context menus. Common implementaton for MSIE and gecko.
1686: */
1687: public static void writeContextMenu(Device device,
1688: SComponent component) throws IOException {
1689: final SPopupMenu menu = component.getComponentPopupMenu();
1690: if (menu != null && menu.isEnabled()) {
1691: final String componentId = menu.getName();
1692: final String popupId = componentId + "_pop";
1693: device
1694: .print(" onContextMenu=\"return wpm_menuPopup(event, '");
1695: device.print(popupId);
1696: device
1697: .print("');\" onMouseDown=\"return wpm_menuPopup(event, '");
1698: device.print(popupId);
1699: device.print("');\"");
1700: }
1701: }
1702:
1703: /**
1704: * Write Tooltip code.
1705: */
1706: public static void writeTooltipMouseOver(Device device,
1707: SComponent component) throws IOException {
1708: final String toolTipText = component != null ? component
1709: .getToolTipText() : null;
1710: if (toolTipText != null && toolTipText.length() > 0) {
1711: device.print(" onmouseover=\"Tip('");
1712: quote(device, toolTipText, true, false, true);
1713: device.print("')\"");
1714: }
1715: }
1716:
1717: public static final boolean hasDimension(final SComponent component) {
1718: SDimension dim = component.getPreferredSize();
1719: return dim != null
1720: && (dim.getHeightInt() != SDimension.AUTO_INT || dim
1721: .getWidthInt() != SDimension.AUTO_INT);
1722: }
1723:
1724: private static class JSArray implements Renderable {
1725: private final List list;
1726:
1727: public JSArray(List list) {
1728: this .list = list;
1729: }
1730:
1731: public void write(Device d) throws IOException {
1732: d.print('[');
1733: final Iterator it = list.iterator();
1734: boolean isFirst = true;
1735: while (it.hasNext()) {
1736: if (!isFirst)
1737: d.print(',');
1738: encodeJS(d, it.next());
1739: isFirst = false;
1740: }
1741: d.print(']');
1742: }
1743:
1744: @Override
1745: public String toString() {
1746: final StringBuilderDevice sb = new StringBuilderDevice(256);
1747: // Prevent this toString() be used for rendering, provide
1748: // info string to break that assumption.
1749: sb.print("JSArray.toString():");
1750: try {
1751: write(sb);
1752: } catch (IOException e) {
1753: }
1754: return sb.toString();
1755: }
1756:
1757: @Override
1758: public boolean equals(Object object) {
1759: return list.equals(object);
1760: }
1761:
1762: @Override
1763: public int hashCode() {
1764: return list.hashCode();
1765: }
1766: }
1767:
1768: private static final class JSObject implements Renderable {
1769: private final Map map;
1770:
1771: public JSObject(Map map) {
1772: this .map = map;
1773: }
1774:
1775: public void write(Device d) throws IOException {
1776: final Iterator it = map.entrySet().iterator();
1777: boolean isFirst = true;
1778: d.print('{');
1779: while (it.hasNext()) {
1780: if (!isFirst)
1781: d.print(',');
1782: Map.Entry entry = (Map.Entry) it.next();
1783: d.print('\'').print(entry.getKey().toString()).print(
1784: "':");
1785: encodeJS(d, entry.getValue());
1786: isFirst = false;
1787: }
1788: d.print('}');
1789: }
1790:
1791: @Override
1792: // TODO: removeme
1793: public String toString() {
1794: final StringBuilderDevice sb = new StringBuilderDevice(10);
1795: // Prevent this toString() be used for rendering, provide
1796: // info string to break that assumption.
1797: sb.print("JSObject.toString():");
1798: try {
1799: write(sb);
1800: } catch (IOException e) {
1801: }
1802: return sb.toString();
1803: }
1804:
1805: @Override
1806: public boolean equals(Object object) {
1807: return map.equals(object);
1808: }
1809:
1810: @Override
1811: public int hashCode() {
1812: return map.hashCode();
1813: }
1814: }
1815: }
|