diff options
author | Tom Tromey <tromey@gcc.gnu.org> | 2007-01-09 19:58:05 +0000 |
---|---|---|
committer | Tom Tromey <tromey@gcc.gnu.org> | 2007-01-09 19:58:05 +0000 |
commit | 97b8365cafc3a344a22d3980b8ed885f5c6d8357 (patch) | |
tree | 996a5f57d4a68c53473382e45cb22f574cb3e4db /libjava/classpath/javax/swing/text/html | |
parent | c648dedbde727ca3f883bb5fd773aa4af70d3369 (diff) | |
download | gcc-97b8365cafc3a344a22d3980b8ed885f5c6d8357.tar.gz |
Merged gcj-eclipse branch to trunk.
From-SVN: r120621
Diffstat (limited to 'libjava/classpath/javax/swing/text/html')
32 files changed, 7916 insertions, 868 deletions
diff --git a/libjava/classpath/javax/swing/text/html/BRView.java b/libjava/classpath/javax/swing/text/html/BRView.java index 5521fed8edf..7d0d5164d49 100644 --- a/libjava/classpath/javax/swing/text/html/BRView.java +++ b/libjava/classpath/javax/swing/text/html/BRView.java @@ -44,8 +44,7 @@ import javax.swing.text.Element; * Handled the HTML BR tag. */ class BRView - extends NullView - + extends InlineView { /** * Creates the new BR view. @@ -66,6 +65,6 @@ class BRView if (axis == X_AXIS) return ForcedBreakWeight; else - return BadBreakWeight; + return super.getBreakWeight(axis, pos, len); } } diff --git a/libjava/classpath/javax/swing/text/html/BlockView.java b/libjava/classpath/javax/swing/text/html/BlockView.java index 6274e7b1756..b05c983e922 100644 --- a/libjava/classpath/javax/swing/text/html/BlockView.java +++ b/libjava/classpath/javax/swing/text/html/BlockView.java @@ -38,9 +38,12 @@ exception statement from your version. */ package javax.swing.text.html; +import gnu.javax.swing.text.html.css.Length; + import java.awt.Graphics; import java.awt.Rectangle; import java.awt.Shape; +import java.util.HashMap; import javax.swing.SizeRequirements; import javax.swing.event.DocumentEvent; @@ -55,7 +58,106 @@ import javax.swing.text.ViewFactory; */ public class BlockView extends BoxView { - + + /** + * Stores information about child positioning according to the + * CSS attributes position, left, right, top and bottom. + */ + private static class PositionInfo + { + // TODO: Use enums when available. + + /** + * Static positioning. This is the default and is thus rarely really + * used. + */ + static final int STATIC = 0; + + /** + * Relative positioning. The box is teaked relative to its static + * computed bounds. + */ + static final int RELATIVE = 1; + + /** + * Absolute positioning. The box is moved relative to the parent's box. + */ + static final int ABSOLUTE = 2; + + /** + * Like ABSOLUTE, with some fixation against the viewport (not yet + * implemented). + */ + static final int FIXED = 3; + + /** + * The type according to the constants of this class. + */ + int type; + + /** + * The left constraint, null if not set. + */ + Length left; + + /** + * The right constraint, null if not set. + */ + Length right; + + /** + * The top constraint, null if not set. + */ + Length top; + + /** + * The bottom constraint, null if not set. + */ + Length bottom; + + /** + * Creates a new PositionInfo object. + * + * @param typ the type to set + * @param l the left constraint + * @param r the right constraint + * @param t the top constraint + * @param b the bottom constraint + */ + PositionInfo(int typ, Length l, Length r, Length t, Length b) + { + type = typ; + left = l; + right = r; + top = t; + bottom = b; + } + } + + /** + * The attributes for this view. + */ + private AttributeSet attributes; + + /** + * The box painter for this view. + * + * This is package private because the TableView needs access to it. + */ + StyleSheet.BoxPainter painter; + + /** + * The width and height as specified in the stylesheet, null if not + * specified. The first value is the X_AXIS, the second the Y_AXIS. You + * can index this directly by the X_AXIS and Y_AXIS constants. + */ + private Length[] cssSpans; + + /** + * Stores additional CSS layout information. + */ + private HashMap positionInfo; + /** * Creates a new view that represents an html box. * This can be used for a number of elements. @@ -66,8 +168,10 @@ public class BlockView extends BoxView public BlockView(Element elem, int axis) { super(elem, axis); + cssSpans = new Length[2]; + positionInfo = new HashMap(); } - + /** * Creates the parent view for this. It is called before * any other methods, if the parent view is working properly. @@ -99,12 +203,27 @@ public class BlockView extends BoxView protected SizeRequirements calculateMajorAxisRequirements(int axis, SizeRequirements r) { - SizeRequirements sr = super.calculateMajorAxisRequirements(axis, r); - // FIXME: adjust it if the CSS width or height attribute is specified - // and applicable - return sr; + if (r == null) + r = new SizeRequirements(); + + if (setCSSSpan(r, axis)) + { + // If we have set the span from CSS, then we need to adjust + // the margins. + SizeRequirements parent = super.calculateMajorAxisRequirements(axis, + null); + int margin = axis == X_AXIS ? getLeftInset() + getRightInset() + : getTopInset() + getBottomInset(); + r.minimum -= margin; + r.preferred -= margin; + r.maximum -= margin; + constrainSize(axis, r, parent); + } + else + r = super.calculateMajorAxisRequirements(axis, r); + return r; } - + /** * Calculates the requirements along the minor axis. * This is implemented to call the superclass and then @@ -118,12 +237,89 @@ public class BlockView extends BoxView protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) { - SizeRequirements sr = super.calculateMinorAxisRequirements(axis, r); - // FIXME: adjust it if the CSS width or height attribute is specified - // and applicable. - return sr; + if (r == null) + r = new SizeRequirements(); + + if (setCSSSpan(r, axis)) + { + // If we have set the span from CSS, then we need to adjust + // the margins. + SizeRequirements parent = super.calculateMinorAxisRequirements(axis, + null); + int margin = axis == X_AXIS ? getLeftInset() + getRightInset() + : getTopInset() + getBottomInset(); + r.minimum -= margin; + r.preferred -= margin; + r.maximum -= margin; + constrainSize(axis, r, parent); + } + else + r = super.calculateMinorAxisRequirements(axis, r); + + // Apply text alignment if appropriate. + if (axis == X_AXIS) + { + Object o = getAttributes().getAttribute(CSS.Attribute.TEXT_ALIGN); + if (o != null) + { + String al = o.toString().trim(); + if (al.equals("center")) + r.alignment = 0.5f; + else if (al.equals("right")) + r.alignment = 1.0f; + else + r.alignment = 0.0f; + } + } + return r; + } + + /** + * Sets the span on the SizeRequirements object according to the + * according CSS span value, when it is set. + * + * @param r the size requirements + * @param axis the axis + * + * @return <code>true</code> when the CSS span has been set, + * <code>false</code> otherwise + */ + private boolean setCSSSpan(SizeRequirements r, int axis) + { + boolean ret = false; + Length span = cssSpans[axis]; + // We can't set relative CSS spans here because we don't know + // yet about the allocated span. Instead we use the view's + // normal requirements. + if (span != null && ! span.isPercentage()) + { + r.minimum = (int) span.getValue(); + r.preferred = (int) span.getValue(); + r.maximum = (int) span.getValue(); + ret = true; + } + return ret; } - + + /** + * Constrains the <code>r</code> requirements according to + * <code>min</code>. + * + * @param axis the axis + * @param r the requirements to constrain + * @param min the constraining requirements + */ + private void constrainSize(int axis, SizeRequirements r, + SizeRequirements min) + { + if (min.minimum > r.minimum) + { + r.minimum = min.minimum; + r.preferred = min.minimum; + r.maximum = Math.max(r.maximum, min.maximum); + } + } + /** * Lays out the box along the minor axis (the axis that is * perpendicular to the axis that it represents). The results @@ -142,10 +338,133 @@ public class BlockView extends BoxView protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) { - // FIXME: Not implemented. - super.layoutMinorAxis(targetSpan, axis, offsets, spans); + int viewCount = getViewCount(); + for (int i = 0; i < viewCount; i++) + { + View view = getView(i); + int min = (int) view.getMinimumSpan(axis); + int max; + // Handle CSS span value of child. + Length length = cssSpans[axis]; + if (length != null) + { + min = Math.max((int) length.getValue(targetSpan), min); + max = min; + } + else + max = (int) view.getMaximumSpan(axis); + + if (max < targetSpan) + { + // Align child. + float align = view.getAlignment(axis); + offsets[i] = (int) ((targetSpan - max) * align); + spans[i] = max; + } + else + { + offsets[i] = 0; + spans[i] = Math.max(min, targetSpan); + } + + // Adjust according to CSS position info. + positionView(targetSpan, axis, i, offsets, spans); + } } - + + /** + * Overridden to perform additional CSS layout (absolute/relative + * positioning). + */ + protected void layoutMajorAxis(int targetSpan, int axis, + int[] offsets, int[] spans) + { + super.layoutMajorAxis(targetSpan, axis, offsets, spans); + + // Adjust according to CSS position info. + int viewCount = getViewCount(); + for (int i = 0; i < viewCount; i++) + { + positionView(targetSpan, axis, i, offsets, spans); + } + } + + /** + * Positions a view according to any additional CSS constraints. + * + * @param targetSpan the target span + * @param axis the axis + * @param i the index of the view + * @param offsets the offsets get placed here + * @param spans the spans get placed here + */ + private void positionView(int targetSpan, int axis, int i, int[] offsets, + int[] spans) + { + View view = getView(i); + PositionInfo pos = (PositionInfo) positionInfo.get(view); + if (pos != null) + { + int p0 = -1; + int p1 = -1; + if (axis == X_AXIS) + { + Length l = pos.left; + if (l != null) + p0 = (int) l.getValue(targetSpan); + l = pos.right; + if (l != null) + p1 = (int) l.getValue(targetSpan); + } + else + { + Length l = pos.top; + if (l != null) + p0 = (int) l.getValue(targetSpan); + l = pos.bottom; + if (l != null) + p1 = (int) l.getValue(targetSpan); + } + if (pos.type == PositionInfo.ABSOLUTE + || pos.type == PositionInfo.FIXED) + { + if (p0 != -1) + { + offsets[i] = p0; + if (p1 != -1) + { + // Overrides computed width. (Possibly overconstrained + // when the width attribute was set too.) + spans[i] = targetSpan - p1 - offsets[i]; + } + } + else if (p1 != -1) + { + // Preserve any computed width. + offsets[i] = targetSpan - p1 - spans[i]; + } + } + else if (pos.type == PositionInfo.RELATIVE) + { + if (p0 != -1) + { + offsets[i] += p0; + if (p1 != -1) + { + // Overrides computed width. (Possibly overconstrained + // when the width attribute was set too.) + spans[i] = spans[i] - p0 - p1 - offsets[i]; + } + } + else if (p1 != -1) + { + // Preserve any computed width. + offsets[i] -= p1; + } + } + } + } + /** * Paints using the given graphics configuration and shape. * This delegates to the css box painter to paint the @@ -156,14 +475,16 @@ public class BlockView extends BoxView */ public void paint(Graphics g, Shape a) { - Rectangle rect = (Rectangle) a; - // FIXME: not fully implemented - getStyleSheet().getBoxPainter(getAttributes()).paint(g, rect.x, rect.y, - rect.width, - rect.height, this); + Rectangle rect = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); + + // Debug output. Shows blocks in green rectangles. + // g.setColor(Color.GREEN); + // g.drawRect(rect.x, rect.y, rect.width, rect.height); + + painter.paint(g, rect.x, rect.y, rect.width, rect.height, this); super.paint(g, a); } - + /** * Fetches the attributes to use when painting. * @@ -171,7 +492,9 @@ public class BlockView extends BoxView */ public AttributeSet getAttributes() { - return getStyleSheet().getViewAttributes(this); + if (attributes == null) + attributes = getStyleSheet().getViewAttributes(this); + return attributes; } /** @@ -200,14 +523,17 @@ public class BlockView extends BoxView public float getAlignment(int axis) { if (axis == X_AXIS) - return 0.0F; + return super.getAlignment(axis); if (axis == Y_AXIS) { if (getViewCount() == 0) return 0.0F; float prefHeight = getPreferredSpan(Y_AXIS); - float firstRowHeight = getView(0).getPreferredSpan(Y_AXIS); - return (firstRowHeight / 2.F) / prefHeight; + View first = getView(0); + float firstRowHeight = first.getPreferredSpan(Y_AXIS); + return prefHeight != 0 ? (firstRowHeight * first.getAlignment(Y_AXIS)) + / prefHeight + : 0; } throw new IllegalArgumentException("Invalid Axis"); } @@ -227,7 +553,8 @@ public class BlockView extends BoxView // If more elements were added, then need to set the properties for them int currPos = ev.getOffset(); - if (currPos <= getStartOffset() && (currPos + ev.getLength()) >= getEndOffset()) + if (currPos <= getStartOffset() + && (currPos + ev.getLength()) >= getEndOffset()) setPropertiesFromAttributes(); } @@ -284,9 +611,33 @@ public class BlockView extends BoxView */ protected void setPropertiesFromAttributes() { - // FIXME: Not implemented (need to use StyleSheet). + // Fetch attributes. + StyleSheet ss = getStyleSheet(); + attributes = ss.getViewAttributes(this); + + // Fetch painter. + painter = ss.getBoxPainter(attributes); + + // Update insets. + if (attributes != null) + { + setInsets((short) painter.getInset(TOP, this), + (short) painter.getInset(LEFT, this), + (short) painter.getInset(BOTTOM, this), + (short) painter.getInset(RIGHT, this)); + } + + // Fetch width and height. + float emBase = ss.getEMBase(attributes); + float exBase = ss.getEXBase(attributes); + cssSpans[X_AXIS] = (Length) attributes.getAttribute(CSS.Attribute.WIDTH); + if (cssSpans[X_AXIS] != null) + cssSpans[X_AXIS].setFontBases(emBase, exBase); + cssSpans[Y_AXIS] = (Length) attributes.getAttribute(CSS.Attribute.HEIGHT); + if (cssSpans[Y_AXIS] != null) + cssSpans[Y_AXIS].setFontBases(emBase, exBase); } - + /** * Gets the default style sheet. * @@ -294,8 +645,77 @@ public class BlockView extends BoxView */ protected StyleSheet getStyleSheet() { - StyleSheet styleSheet = new StyleSheet(); - styleSheet.importStyleSheet(getClass().getResource(HTMLEditorKit.DEFAULT_CSS)); - return styleSheet; + HTMLDocument doc = (HTMLDocument) getDocument(); + return doc.getStyleSheet(); + } + + /** + * Overridden to fetch additional CSS layout information. + */ + public void replace(int offset, int length, View[] views) + { + // First remove unneeded stuff. + for (int i = 0; i < length; i++) + { + View child = getView(i + offset); + positionInfo.remove(child); + } + + // Call super to actually replace the views. + super.replace(offset, length, views); + + // Now fetch the position infos for the new views. + for (int i = 0; i < views.length; i++) + { + fetchLayoutInfo(views[i]); + } + } + + /** + * Fetches and stores the layout info for the specified view. + * + * @param view the view for which the layout info is stored + */ + private void fetchLayoutInfo(View view) + { + AttributeSet atts = view.getAttributes(); + Object o = atts.getAttribute(CSS.Attribute.POSITION); + if (o != null && o instanceof String && ! o.equals("static")) + { + int type; + if (o.equals("relative")) + type = PositionInfo.RELATIVE; + else if (o.equals("absolute")) + type = PositionInfo.ABSOLUTE; + else if (o.equals("fixed")) + type = PositionInfo.FIXED; + else + type = PositionInfo.STATIC; + + if (type != PositionInfo.STATIC) + { + StyleSheet ss = getStyleSheet(); + float emBase = ss.getEMBase(atts); + float exBase = ss.getEXBase(atts); + Length left = (Length) atts.getAttribute(CSS.Attribute.LEFT); + if (left != null) + left.setFontBases(emBase, exBase); + Length right = (Length) atts.getAttribute(CSS.Attribute.RIGHT); + if (right != null) + right.setFontBases(emBase, exBase); + Length top = (Length) atts.getAttribute(CSS.Attribute.TOP); + if (top != null) + top.setFontBases(emBase, exBase); + Length bottom = (Length) atts.getAttribute(CSS.Attribute.BOTTOM); + if (bottom != null) + bottom.setFontBases(emBase, exBase); + if (left != null || right != null || top != null || bottom != null) + { + PositionInfo pos = new PositionInfo(type, left, right, top, + bottom); + positionInfo.put(view, pos); + } + } + } } } diff --git a/libjava/classpath/javax/swing/text/html/CSS.java b/libjava/classpath/javax/swing/text/html/CSS.java index c248e758ec2..77f94a60878 100644 --- a/libjava/classpath/javax/swing/text/html/CSS.java +++ b/libjava/classpath/javax/swing/text/html/CSS.java @@ -37,8 +37,19 @@ exception statement from your version. */ package javax.swing.text.html; +import gnu.javax.swing.text.html.css.BorderStyle; +import gnu.javax.swing.text.html.css.BorderWidth; +import gnu.javax.swing.text.html.css.CSSColor; +import gnu.javax.swing.text.html.css.FontSize; +import gnu.javax.swing.text.html.css.FontStyle; +import gnu.javax.swing.text.html.css.FontWeight; +import gnu.javax.swing.text.html.css.Length; + import java.io.Serializable; import java.util.HashMap; +import java.util.StringTokenizer; + +import javax.swing.text.MutableAttributeSet; /** * Provides CSS attributes to be used by the HTML view classes. The constants @@ -388,6 +399,36 @@ public class CSS implements Serializable public static final Attribute WORD_SPACING = new Attribute("word-spacing", true, "normal"); + // Some GNU Classpath specific extensions. + static final Attribute BORDER_TOP_STYLE = + new Attribute("border-top-style", false, null); + static final Attribute BORDER_BOTTOM_STYLE = + new Attribute("border-bottom-style", false, null); + static final Attribute BORDER_LEFT_STYLE = + new Attribute("border-left-style", false, null); + static final Attribute BORDER_RIGHT_STYLE = + new Attribute("border-right-style", false, null); + static final Attribute BORDER_TOP_COLOR = + new Attribute("border-top-color", false, null); + static final Attribute BORDER_BOTTOM_COLOR = + new Attribute("border-bottom-color", false, null); + static final Attribute BORDER_LEFT_COLOR = + new Attribute("border-left-color", false, null); + static final Attribute BORDER_RIGHT_COLOR = + new Attribute("border-right-color", false, null); + static final Attribute BORDER_SPACING = + new Attribute("border-spacing", false, null); + static final Attribute POSITION = + new Attribute("position", false, null); + static final Attribute LEFT = + new Attribute("left", false, null); + static final Attribute RIGHT = + new Attribute("right", false, null); + static final Attribute TOP = + new Attribute("top", false, null); + static final Attribute BOTTOM = + new Attribute("bottom", false, null); + /** * The attribute string. */ @@ -459,4 +500,237 @@ public class CSS implements Serializable return defaultValue; } } + + /** + * Maps attribute values (String) to some converter class, based on the + * key. + * + * @param att the key + * @param v the value + * + * @return the wrapped value + */ + static Object getValue(Attribute att, String v) + { + Object o; + if (att == Attribute.FONT_SIZE) + o = new FontSize(v); + else if (att == Attribute.FONT_WEIGHT) + o = new FontWeight(v); + else if (att == Attribute.FONT_STYLE) + o = new FontStyle(v); + else if (att == Attribute.COLOR || att == Attribute.BACKGROUND_COLOR + || att == Attribute.BORDER_COLOR + || att == Attribute.BORDER_TOP_COLOR + || att == Attribute.BORDER_BOTTOM_COLOR + || att == Attribute.BORDER_LEFT_COLOR + || att == Attribute.BORDER_RIGHT_COLOR) + o = new CSSColor(v); + else if (att == Attribute.MARGIN || att == Attribute.MARGIN_BOTTOM + || att == Attribute.MARGIN_LEFT || att == Attribute.MARGIN_RIGHT + || att == Attribute.MARGIN_TOP || att == Attribute.WIDTH + || att == Attribute.HEIGHT + || att == Attribute.PADDING || att == Attribute.PADDING_BOTTOM + || att == Attribute.PADDING_LEFT || att == Attribute.PADDING_RIGHT + || att == Attribute.PADDING_TOP + || att == Attribute.LEFT || att == Attribute.RIGHT + || att == Attribute.TOP || att == Attribute.BOTTOM) + o = new Length(v); + else if (att == Attribute.BORDER_WIDTH || att == Attribute.BORDER_TOP_WIDTH + || att == Attribute.BORDER_LEFT_WIDTH + || att == Attribute.BORDER_RIGHT_WIDTH + || att == Attribute.BORDER_BOTTOM_WIDTH) + o = new BorderWidth(v); + else + o = v; + return o; + } + + static void addInternal(MutableAttributeSet atts, Attribute a, String v) + { + if (a == Attribute.BACKGROUND) + parseBackgroundShorthand(atts, v); + else if (a == Attribute.PADDING) + parsePaddingShorthand(atts, v); + else if (a == Attribute.MARGIN) + parseMarginShorthand(atts, v); + else if (a == Attribute.BORDER || a == Attribute.BORDER_LEFT + || a == Attribute.BORDER_RIGHT || a == Attribute.BORDER_TOP + || a == Attribute.BORDER_BOTTOM) + parseBorderShorthand(atts, v, a); + } + + /** + * Parses the background shorthand and translates it to more specific + * background attributes. + * + * @param atts the attributes + * @param v the value + */ + private static void parseBackgroundShorthand(MutableAttributeSet atts, + String v) + { + StringTokenizer tokens = new StringTokenizer(v, " "); + while (tokens.hasMoreElements()) + { + String token = tokens.nextToken(); + if (CSSColor.isValidColor(token)) + atts.addAttribute(Attribute.BACKGROUND_COLOR, + new CSSColor(token)); + } + } + + /** + * Parses the padding shorthand and translates to the specific padding + * values. + * + * @param atts the attributes + * @param v the actual value + */ + private static void parsePaddingShorthand(MutableAttributeSet atts, String v) + { + StringTokenizer tokens = new StringTokenizer(v, " "); + int numTokens = tokens.countTokens(); + if (numTokens == 1) + { + Length l = new Length(tokens.nextToken()); + atts.addAttribute(Attribute.PADDING_BOTTOM, l); + atts.addAttribute(Attribute.PADDING_LEFT, l); + atts.addAttribute(Attribute.PADDING_RIGHT, l); + atts.addAttribute(Attribute.PADDING_TOP, l); + } + else if (numTokens == 2) + { + Length l1 = new Length(tokens.nextToken()); + Length l2 = new Length(tokens.nextToken()); + atts.addAttribute(Attribute.PADDING_BOTTOM, l1); + atts.addAttribute(Attribute.PADDING_TOP, l1); + atts.addAttribute(Attribute.PADDING_LEFT, l2); + atts.addAttribute(Attribute.PADDING_RIGHT, l2); + } + else if (numTokens == 3) + { + Length l1 = new Length(tokens.nextToken()); + Length l2 = new Length(tokens.nextToken()); + Length l3 = new Length(tokens.nextToken()); + atts.addAttribute(Attribute.PADDING_TOP, l1); + atts.addAttribute(Attribute.PADDING_LEFT, l2); + atts.addAttribute(Attribute.PADDING_RIGHT, l2); + atts.addAttribute(Attribute.PADDING_BOTTOM, l3); + } + else + { + Length l1 = new Length(tokens.nextToken()); + Length l2 = new Length(tokens.nextToken()); + Length l3 = new Length(tokens.nextToken()); + Length l4 = new Length(tokens.nextToken()); + atts.addAttribute(Attribute.PADDING_TOP, l1); + atts.addAttribute(Attribute.PADDING_RIGHT, l2); + atts.addAttribute(Attribute.PADDING_BOTTOM, l3); + atts.addAttribute(Attribute.PADDING_LEFT, l4); + } + } + + /** + * Parses the margin shorthand and translates to the specific margin + * values. + * + * @param atts the attributes + * @param v the actual value + */ + private static void parseMarginShorthand(MutableAttributeSet atts, String v) + { + StringTokenizer tokens = new StringTokenizer(v, " "); + int numTokens = tokens.countTokens(); + if (numTokens == 1) + { + Length l = new Length(tokens.nextToken()); + atts.addAttribute(Attribute.MARGIN_BOTTOM, l); + atts.addAttribute(Attribute.MARGIN_LEFT, l); + atts.addAttribute(Attribute.MARGIN_RIGHT, l); + atts.addAttribute(Attribute.MARGIN_TOP, l); + } + else if (numTokens == 2) + { + Length l1 = new Length(tokens.nextToken()); + Length l2 = new Length(tokens.nextToken()); + atts.addAttribute(Attribute.MARGIN_BOTTOM, l1); + atts.addAttribute(Attribute.MARGIN_TOP, l1); + atts.addAttribute(Attribute.MARGIN_LEFT, l2); + atts.addAttribute(Attribute.MARGIN_RIGHT, l2); + } + else if (numTokens == 3) + { + Length l1 = new Length(tokens.nextToken()); + Length l2 = new Length(tokens.nextToken()); + Length l3 = new Length(tokens.nextToken()); + atts.addAttribute(Attribute.MARGIN_TOP, l1); + atts.addAttribute(Attribute.MARGIN_LEFT, l2); + atts.addAttribute(Attribute.MARGIN_RIGHT, l2); + atts.addAttribute(Attribute.MARGIN_BOTTOM, l3); + } + else + { + Length l1 = new Length(tokens.nextToken()); + Length l2 = new Length(tokens.nextToken()); + Length l3 = new Length(tokens.nextToken()); + Length l4 = new Length(tokens.nextToken()); + atts.addAttribute(Attribute.MARGIN_TOP, l1); + atts.addAttribute(Attribute.MARGIN_RIGHT, l2); + atts.addAttribute(Attribute.MARGIN_BOTTOM, l3); + atts.addAttribute(Attribute.MARGIN_LEFT, l4); + } + } + + /** + * Parses the CSS border shorthand attribute and translates it to the + * more specific border attributes. + * + * @param atts the attribute + * @param value the value + */ + private static void parseBorderShorthand(MutableAttributeSet atts, + String value, Attribute cssAtt) + { + StringTokenizer tokens = new StringTokenizer(value, " "); + while (tokens.hasMoreTokens()) + { + String token = tokens.nextToken(); + if (BorderStyle.isValidStyle(token)) + { + if (cssAtt == Attribute.BORDER_LEFT || cssAtt == Attribute.BORDER) + atts.addAttribute(Attribute.BORDER_LEFT_STYLE, token); + if (cssAtt == Attribute.BORDER_RIGHT || cssAtt == Attribute.BORDER) + atts.addAttribute(Attribute.BORDER_RIGHT_STYLE, token); + if (cssAtt == Attribute.BORDER_BOTTOM || cssAtt == Attribute.BORDER) + atts.addAttribute(Attribute.BORDER_BOTTOM_STYLE, token); + if (cssAtt == Attribute.BORDER_TOP || cssAtt == Attribute.BORDER) + atts.addAttribute(Attribute.BORDER_TOP_STYLE, token); + } + else if (BorderWidth.isValid(token)) + { + BorderWidth w = new BorderWidth(token); + if (cssAtt == Attribute.BORDER_LEFT || cssAtt == Attribute.BORDER) + atts.addAttribute(Attribute.BORDER_LEFT_WIDTH, w); + if (cssAtt == Attribute.BORDER_RIGHT || cssAtt == Attribute.BORDER) + atts.addAttribute(Attribute.BORDER_RIGHT_WIDTH, w); + if (cssAtt == Attribute.BORDER_BOTTOM || cssAtt == Attribute.BORDER) + atts.addAttribute(Attribute.BORDER_BOTTOM_WIDTH, w); + if (cssAtt == Attribute.BORDER_TOP || cssAtt == Attribute.BORDER) + atts.addAttribute(Attribute.BORDER_TOP_WIDTH, w); + } + else if (CSSColor.isValidColor(token)) + { + CSSColor c = new CSSColor(token); + if (cssAtt == Attribute.BORDER_LEFT || cssAtt == Attribute.BORDER) + atts.addAttribute(Attribute.BORDER_LEFT_COLOR, c); + if (cssAtt == Attribute.BORDER_RIGHT || cssAtt == Attribute.BORDER) + atts.addAttribute(Attribute.BORDER_RIGHT_COLOR, c); + if (cssAtt == Attribute.BORDER_BOTTOM || cssAtt == Attribute.BORDER) + atts.addAttribute(Attribute.BORDER_BOTTOM_COLOR, c); + if (cssAtt == Attribute.BORDER_TOP || cssAtt == Attribute.BORDER) + atts.addAttribute(Attribute.BORDER_TOP_COLOR, c); + } + } + } } diff --git a/libjava/classpath/javax/swing/text/html/CSSBorder.java b/libjava/classpath/javax/swing/text/html/CSSBorder.java new file mode 100644 index 00000000000..fff6b01a170 --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/CSSBorder.java @@ -0,0 +1,421 @@ +/* CSSBorder.java -- A border for rendering CSS border styles + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import gnu.javax.swing.text.html.css.BorderWidth; +import gnu.javax.swing.text.html.css.CSSColor; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Insets; + +import javax.swing.border.Border; +import javax.swing.text.AttributeSet; + +/** + * A border implementation to render CSS border styles. + */ +class CSSBorder + implements Border +{ + + /** + * The CSS border styles. + */ + + private static final int STYLE_NOT_SET = -1; + private static final int STYLE_NONE = 0; + private static final int STYLE_HIDDEN = 1; + private static final int STYLE_DOTTED = 2; + private static final int STYLE_DASHED = 3; + private static final int STYLE_SOLID = 4; + private static final int STYLE_DOUBLE = 5; + private static final int STYLE_GROOVE = 6; + private static final int STYLE_RIDGE = 7; + private static final int STYLE_INSET = 8; + private static final int STYLE_OUTSET = 9; + + /** + * The left insets. + */ + private int left; + + /** + * The right insets. + */ + private int right; + + /** + * The top insets. + */ + private int top; + + /** + * The bottom insets. + */ + private int bottom; + + /** + * The border style on the left. + */ + private int leftStyle; + + /** + * The border style on the right. + */ + private int rightStyle; + + /** + * The border style on the top. + */ + private int topStyle; + + /** + * The color for the top border. + */ + private Color topColor; + + /** + * The color for the bottom border. + */ + private Color bottomColor; + + /** + * The color for the left border. + */ + private Color leftColor; + + /** + * The color for the right border. + */ + private Color rightColor; + + /** + * The border style on the bottom. + */ + private int bottomStyle; + + /** + * Creates a new CSS border and fetches its attributes from the specified + * attribute set. + * + * @param atts the attribute set that contains the border spec + */ + CSSBorder(AttributeSet atts, StyleSheet ss) + { + // Determine the border styles. + int style = getBorderStyle(atts, CSS.Attribute.BORDER_STYLE); + if (style == STYLE_NOT_SET) + style = STYLE_NONE; // Default to none. + topStyle = bottomStyle = leftStyle = rightStyle = style; + style = getBorderStyle(atts, CSS.Attribute.BORDER_TOP_STYLE); + if (style != STYLE_NOT_SET) + topStyle = style; + style = getBorderStyle(atts, CSS.Attribute.BORDER_BOTTOM_STYLE); + if (style != STYLE_NOT_SET) + bottomStyle = style; + style = getBorderStyle(atts, CSS.Attribute.BORDER_LEFT_STYLE); + if (style != STYLE_NOT_SET) + leftStyle = style; + style = getBorderStyle(atts, CSS.Attribute.BORDER_RIGHT_STYLE); + if (style != STYLE_NOT_SET) + rightStyle = style; + + // Determine the border colors. + Color color = getBorderColor(atts, CSS.Attribute.BORDER_COLOR); + if (color == null) + color = Color.BLACK; + topColor = bottomColor = leftColor = rightColor = color; + color = getBorderColor(atts, CSS.Attribute.BORDER_TOP_COLOR); + if (color != null) + topColor = color; + color = getBorderColor(atts, CSS.Attribute.BORDER_BOTTOM_COLOR); + if (color != null) + bottomColor = color; + color = getBorderColor(atts, CSS.Attribute.BORDER_LEFT_COLOR); + if (color != null) + leftColor = color; + color = getBorderColor(atts, CSS.Attribute.BORDER_RIGHT_COLOR); + if (color != null) + rightColor = color; + + // Determine the border widths. + int width = getBorderWidth(atts, CSS.Attribute.BORDER_WIDTH, ss); + if (width == -1) + width = 0; + top = bottom = left = right = width; + width = getBorderWidth(atts, CSS.Attribute.BORDER_TOP_WIDTH, ss); + if (width >= 0) + top = width; + width = getBorderWidth(atts, CSS.Attribute.BORDER_BOTTOM_WIDTH, ss); + if (width >= 0) + bottom = width; + width = getBorderWidth(atts, CSS.Attribute.BORDER_LEFT_WIDTH, ss); + if (width >= 0) + left = width; + width = getBorderWidth(atts, CSS.Attribute.BORDER_RIGHT_WIDTH, ss); + if (width >= 0) + right = width; + } + + /** + * Determines the border style for a given CSS attribute. + * + * @param atts the attribute set + * @param key the CSS key + * + * @return the border style according to the constants defined in this class + */ + private int getBorderStyle(AttributeSet atts, CSS.Attribute key) + { + int style = STYLE_NOT_SET; + Object o = atts.getAttribute(key); + if (o != null) + { + String cssStyle = o.toString(); + if (cssStyle.equals("none")) + style = STYLE_NONE; + else if (cssStyle.equals("hidden")) + style = STYLE_HIDDEN; + else if (cssStyle.equals("dotted")) + style = STYLE_DOTTED; + else if (cssStyle.equals("dashed")) + style = STYLE_DASHED; + else if (cssStyle.equals("solid")) + style = STYLE_SOLID; + else if (cssStyle.equals("double")) + style = STYLE_DOUBLE; + else if (cssStyle.equals("groove")) + style = STYLE_GROOVE; + else if (cssStyle.equals("ridge")) + style = STYLE_RIDGE; + else if (cssStyle.equals("inset")) + style = STYLE_INSET; + else if (cssStyle.equals("outset")) + style = STYLE_OUTSET; + } + return style; + } + + /** + * Determines the border color for the specified key. + * + * @param atts the attribute set from which to fetch the color + * @param key the CSS key + * + * @return the border color + */ + private Color getBorderColor(AttributeSet atts, CSS.Attribute key) + { + Object o = atts.getAttribute(key); + Color color = null; + if (o instanceof CSSColor) + { + CSSColor cssColor = (CSSColor) o; + color = cssColor.getValue(); + } + return color; + } + + /** + * Returns the width for the specified key. + * + * @param atts the attributes to fetch the width from + * @param key the CSS key + * + * @return the width, or -1 of none has been set + */ + private int getBorderWidth(AttributeSet atts, CSS.Attribute key, + StyleSheet ss) + { + int width = -1; + Object o = atts.getAttribute(key); + if (o instanceof BorderWidth) + { + BorderWidth w = (BorderWidth) o; + w.setFontBases(ss.getEMBase(atts), ss.getEXBase(atts)); + width = (int) ((BorderWidth) o).getValue(); + } + return width; + } + + /** + * Returns the border insets. + */ + public Insets getBorderInsets(Component c) + { + return new Insets(top, left, bottom, right); + } + + /** + * CSS borders are generally opaque so return true here. + */ + public boolean isBorderOpaque() + { + return true; + } + + public void paintBorder(Component c, Graphics g, int x, int y, int width, + int height) + { + // Top border. + paintBorderLine(g, x, y + top / 2, x + width, y + top / 2, topStyle, top, + topColor, false); + // Left border. + paintBorderLine(g, x + left / 2, y, x + left / 2, y + height, leftStyle, + left, leftColor, true); + // Bottom border. + paintBorderLine(g, x, y + height - bottom / 2, x + width, + y + height - bottom / 2, topStyle, bottom, bottomColor, + false); + // Right border. + paintBorderLine(g, x + width - right / 2, y, x + width - right / 2, + y + height, topStyle, right, rightColor, true); + + } + + private void paintBorderLine(Graphics g, int x1, int y1, int x2, int y2, + int style, int width, Color color, + boolean vertical) + { + switch (style) + { + case STYLE_DOTTED: + paintDottedLine(g, x1, y1, x2, y2, width, color, vertical); + break; + case STYLE_DASHED: + paintDashedLine(g, x1, y1, x2, y2, width, color, vertical); + break; + case STYLE_SOLID: + paintSolidLine(g, x1, y1, x2, y2, width, color, vertical); + break; + case STYLE_DOUBLE: + paintDoubleLine(g, x1, y1, x2, y2, width, color, vertical); + break; + case STYLE_GROOVE: + paintGrooveLine(g, x1, y1, x2, y2, width, color, vertical); + break; + case STYLE_RIDGE: + paintRidgeLine(g, x1, y1, x2, y2, width, color, vertical); + break; + case STYLE_OUTSET: + paintOutsetLine(g, x1, y1, x2, y2, width, color, vertical); + break; + case STYLE_INSET: + paintInsetLine(g, x1, y1, x2, y2, width, color, vertical); + break; + case STYLE_NONE: + case STYLE_HIDDEN: + default: + // Nothing to do in these cases. + } + } + + private void paintDottedLine(Graphics g, int x1, int y1, int x2, int y2, + int width, Color color, boolean vertical) + { + // FIXME: Implement this. + paintSolidLine(g, x1, y1, x2, y2, width, color, vertical); + } + + private void paintDashedLine(Graphics g, int x1, int y1, int x2, int y2, + int width, Color color, boolean vertical) + { + // FIXME: Implement this. + paintSolidLine(g, x1, y1, x2, y2, width, color, vertical); + } + + private void paintSolidLine(Graphics g, int x1, int y1, int x2, int y2, + int width, Color color, boolean vertical) + { + int x = Math.min(x1, x2); + int y = Math.min(y1, y1); + int w = Math.abs(x2 - x1); + int h = Math.abs(y2 - y1); + if (vertical) + { + w = width; + x -= width / 2; + } + else + { + h = width; + y -= width / 2; + } + g.setColor(color); + g.fillRect(x, y, w, h); + } + + private void paintDoubleLine(Graphics g, int x1, int y1, int x2, int y2, + int width, Color color, boolean vertical) + { + // FIXME: Implement this. + paintSolidLine(g, x1, y1, x2, y2, width, color, vertical); + } + + private void paintGrooveLine(Graphics g, int x1, int y1, int x2, int y2, + int width, Color color, boolean vertical) + { + // FIXME: Implement this. + paintSolidLine(g, x1, y1, x2, y2, width, color, vertical); + } + + private void paintRidgeLine(Graphics g, int x1, int y1, int x2, int y2, + int width, Color color, boolean vertical) + { + // FIXME: Implement this. + paintSolidLine(g, x1, y1, x2, y2, width, color, vertical); + } + + private void paintOutsetLine(Graphics g, int x1, int y1, int x2, int y2, + int width, Color color, boolean vertical) + { + // FIXME: Implement this. + paintSolidLine(g, x1, y1, x2, y2, width, color, vertical); + } + + private void paintInsetLine(Graphics g, int x1, int y1, int x2, int y2, + int width, Color color, boolean vertical) + { + // FIXME: Implement this. + paintSolidLine(g, x1, y1, x2, y2, width, color, vertical); + } + +} diff --git a/libjava/classpath/javax/swing/text/html/FormSubmitEvent.java b/libjava/classpath/javax/swing/text/html/FormSubmitEvent.java new file mode 100644 index 00000000000..bc7c36f4b27 --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/FormSubmitEvent.java @@ -0,0 +1,123 @@ +/* FormSubmitEvent.java -- Event fired on form submit + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import java.net.URL; + +import javax.swing.text.Element; + +/** + * The event fired on form submit. + * + * @since 1.5 + */ +public class FormSubmitEvent + extends HTMLFrameHyperlinkEvent +{ + + // FIXME: Use enums when available. + /** + * The submit method. + */ + public static class MethodType + { + /** + * Indicates a form submit with HTTP method POST. + */ + public static final MethodType POST = new MethodType(); + + /** + * Indicates a form submit with HTTP method GET. + */ + public static final MethodType GET = new MethodType(); + + private MethodType() + { + } + } + + /** + * The submit method. + */ + private MethodType method; + + /** + * The actual submit data. + */ + private String data; + + /** + * Creates a new FormSubmitEvent. + * + * @param source the source + * @param type the type of hyperlink update + * @param url the action url + * @param el the associated element + * @param target the target attribute + * @param m the submit method + * @param d the submit data + */ + FormSubmitEvent(Object source, EventType type, URL url, Element el, + String target, MethodType m, String d) + { + super(source, type, url, el, target); + method = m; + data = d; + } + + /** + * Returns the submit data. + * + * @return the submit data + */ + public String getData() + { + return data; + } + + /** + * Returns the HTTP submit method. + * + * @return the HTTP submit method + */ + public MethodType getMethod() + { + return method; + } +} diff --git a/libjava/classpath/javax/swing/text/html/FormView.java b/libjava/classpath/javax/swing/text/html/FormView.java index d54021066d0..ef362bd3d9b 100644 --- a/libjava/classpath/javax/swing/text/html/FormView.java +++ b/libjava/classpath/javax/swing/text/html/FormView.java @@ -44,16 +44,36 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; +import javax.swing.ButtonModel; +import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JEditorPane; +import javax.swing.JList; import javax.swing.JPasswordField; import javax.swing.JRadioButton; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; import javax.swing.JTextField; +import javax.swing.ListSelectionModel; +import javax.swing.SwingUtilities; import javax.swing.UIManager; +import javax.swing.event.HyperlinkEvent; import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; import javax.swing.text.ComponentView; +import javax.swing.text.Document; import javax.swing.text.Element; +import javax.swing.text.ElementIterator; import javax.swing.text.StyleConstants; /** @@ -105,6 +125,231 @@ public class FormView } /** + * Actually submits the form data. + */ + private class SubmitThread + extends Thread + { + /** + * The submit data. + */ + private String data; + + /** + * Creates a new SubmitThread. + * + * @param d the submit data + */ + SubmitThread(String d) + { + data = d; + } + + /** + * Actually performs the submit. + */ + public void run() + { + if (data.length() > 0) + { + final String method = getMethod(); + final URL actionURL = getActionURL(); + final String target = getTarget(); + URLConnection conn; + final JEditorPane editor = (JEditorPane) getContainer(); + final HTMLDocument doc = (HTMLDocument) editor.getDocument(); + HTMLEditorKit kit = (HTMLEditorKit) editor.getEditorKit(); + if (kit.isAutoFormSubmission()) + { + try + { + final URL url; + if (method != null && method.equals("post")) + { + // Perform POST. + url = actionURL; + conn = url.openConnection(); + postData(conn, data); + } + else + { + // Default to GET. + url = new URL(actionURL + "?" + data); + } + Runnable loadDoc = new Runnable() + { + public void run() + { + if (doc.isFrameDocument()) + { + editor.fireHyperlinkUpdate(createSubmitEvent(method, + actionURL, + target)); + } + else + { + try + { + editor.setPage(url); + } + catch (IOException ex) + { + // Oh well. + ex.printStackTrace(); + } + } + } + }; + SwingUtilities.invokeLater(loadDoc); + } + catch (MalformedURLException ex) + { + ex.printStackTrace(); + } + catch (IOException ex) + { + ex.printStackTrace(); + } + } + else + { + editor.fireHyperlinkUpdate(createSubmitEvent(method,actionURL, + target)); + } + } + } + + /** + * Determines the submit method. + * + * @return the submit method + */ + private String getMethod() + { + AttributeSet formAtts = getFormAttributes(); + String method = null; + if (formAtts != null) + { + method = (String) formAtts.getAttribute(HTML.Attribute.METHOD); + } + return method; + } + + /** + * Determines the action URL. + * + * @return the action URL + */ + private URL getActionURL() + { + AttributeSet formAtts = getFormAttributes(); + HTMLDocument doc = (HTMLDocument) getElement().getDocument(); + URL url = doc.getBase(); + if (formAtts != null) + { + String action = + (String) formAtts.getAttribute(HTML.Attribute.ACTION); + if (action != null) + { + try + { + url = new URL(url, action); + } + catch (MalformedURLException ex) + { + url = null; + } + } + } + return url; + } + + /** + * Fetches the target attribute. + * + * @return the target attribute or _self if none is present + */ + private String getTarget() + { + AttributeSet formAtts = getFormAttributes(); + String target = null; + if (formAtts != null) + { + target = (String) formAtts.getAttribute(HTML.Attribute.TARGET); + if (target != null) + target = target.toLowerCase(); + } + if (target == null) + target = "_self"; + return target; + } + + /** + * Posts the form data over the specified connection. + * + * @param conn the connection + */ + private void postData(URLConnection conn, String data) + { + conn.setDoOutput(true); + PrintWriter out = null; + try + { + out = new PrintWriter(new OutputStreamWriter(conn.getOutputStream())); + out.print(data); + out.flush(); + } + catch (IOException ex) + { + // Deal with this! + ex.printStackTrace(); + } + finally + { + if (out != null) + out.close(); + } + } + + /** + * Determines the attributes from the relevant form tag. + * + * @return the attributes from the relevant form tag, <code>null</code> + * when there is no form tag + */ + private AttributeSet getFormAttributes() + { + AttributeSet atts = null; + Element form = getFormElement(); + if (form != null) + atts = form.getAttributes(); + return atts; + } + + /** + * Creates the submit event that should be fired. + * + * This is package private to avoid accessor methods. + * + * @param method the submit method + * @param actionURL the action URL + * @param target the target + * + * @return the submit event + */ + FormSubmitEvent createSubmitEvent(String method, URL actionURL, + String target) + { + FormSubmitEvent.MethodType m = "post".equals(method) + ? FormSubmitEvent.MethodType.POST + : FormSubmitEvent.MethodType.GET; + return new FormSubmitEvent(FormView.this, + HyperlinkEvent.EventType.ACTIVATED, + actionURL, getElement(), target, m, data); + } + } + + /** * If the value attribute of an <code><input type="submit">> * tag is not specified, then this string is used. * @@ -125,6 +370,11 @@ public class FormView UIManager.getString("FormView.resetButtonText"); /** + * If this is true, the maximum size is set to the preferred size. + */ + private boolean maxIsPreferred; + + /** * Creates a new <code>FormView</code>. * * @param el the element that is displayed by this view. @@ -141,39 +391,161 @@ public class FormView { Component comp = null; Element el = getElement(); - Object tag = el.getAttributes().getAttribute(StyleConstants.NameAttribute); + AttributeSet atts = el.getAttributes(); + Object tag = atts.getAttribute(StyleConstants.NameAttribute); + Object model = atts.getAttribute(StyleConstants.ModelAttribute); if (tag.equals(HTML.Tag.INPUT)) { - AttributeSet atts = el.getAttributes(); String type = (String) atts.getAttribute(HTML.Attribute.TYPE); - String value = (String) atts.getAttribute(HTML.Attribute.VALUE); if (type.equals("button")) - comp = new JButton(value); + { + String value = (String) atts.getAttribute(HTML.Attribute.VALUE); + JButton b = new JButton(value); + if (model != null) + { + b.setModel((ButtonModel) model); + b.addActionListener(this); + } + comp = b; + maxIsPreferred = true; + } else if (type.equals("checkbox")) - comp = new JCheckBox(value); + { + if (model instanceof ResetableToggleButtonModel) + { + ResetableToggleButtonModel m = + (ResetableToggleButtonModel) model; + JCheckBox c = new JCheckBox(); + c.setModel(m); + comp = c; + maxIsPreferred = true; + } + } else if (type.equals("image")) - comp = new JButton(value); // FIXME: Find out how to fetch the image. + { + String src = (String) atts.getAttribute(HTML.Attribute.SRC); + JButton b; + try + { + URL base = ((HTMLDocument) el.getDocument()).getBase(); + URL srcURL = new URL(base, src); + ImageIcon icon = new ImageIcon(srcURL); + b = new JButton(icon); + } + catch (MalformedURLException ex) + { + b = new JButton(src); + } + if (model != null) + { + b.setModel((ButtonModel) model); + b.addActionListener(this); + } + comp = b; + maxIsPreferred = true; + } else if (type.equals("password")) - comp = new JPasswordField(value); + { + int size = HTML.getIntegerAttributeValue(atts, HTML.Attribute.SIZE, + -1); + JTextField tf = new JPasswordField(); + if (size > 0) + tf.setColumns(size); + else + tf.setColumns(20); + if (model != null) + tf.setDocument((Document) model); + tf.addActionListener(this); + comp = tf; + maxIsPreferred = true; + } else if (type.equals("radio")) - comp = new JRadioButton(value); + { + if (model instanceof ResetableToggleButtonModel) + { + ResetableToggleButtonModel m = + (ResetableToggleButtonModel) model; + JRadioButton c = new JRadioButton(); + c.setModel(m); + comp = c; + maxIsPreferred = true; + } + } else if (type.equals("reset")) { - if (value == null || value.equals("")) - value = RESET; - comp = new JButton(value); + String value = (String) atts.getAttribute(HTML.Attribute.VALUE); + if (value == null) + value = UIManager.getString("FormView.resetButtonText"); + JButton b = new JButton(value); + if (model != null) + { + b.setModel((ButtonModel) model); + b.addActionListener(this); + } + comp = b; + maxIsPreferred = true; } else if (type.equals("submit")) { - if (value == null || value.equals("")) - value = SUBMIT; - comp = new JButton(value); + String value = (String) atts.getAttribute(HTML.Attribute.VALUE); + if (value == null) + value = UIManager.getString("FormView.submitButtonText"); + JButton b = new JButton(value); + if (model != null) + { + b.setModel((ButtonModel) model); + b.addActionListener(this); + } + comp = b; + maxIsPreferred = true; } else if (type.equals("text")) - comp = new JTextField(value); - + { + int size = HTML.getIntegerAttributeValue(atts, HTML.Attribute.SIZE, + -1); + JTextField tf = new JTextField(); + if (size > 0) + tf.setColumns(size); + else + tf.setColumns(20); + if (model != null) + tf.setDocument((Document) model); + tf.addActionListener(this); + comp = tf; + maxIsPreferred = true; + } + } + else if (tag == HTML.Tag.TEXTAREA) + { + JTextArea textArea = new JTextArea((Document) model); + int rows = HTML.getIntegerAttributeValue(atts, HTML.Attribute.ROWS, 1); + textArea.setRows(rows); + int cols = HTML.getIntegerAttributeValue(atts, HTML.Attribute.COLS, 20); + textArea.setColumns(cols); + maxIsPreferred = true; + comp = new JScrollPane(textArea, + JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, + JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); + } + else if (tag == HTML.Tag.SELECT) + { + if (model instanceof SelectListModel) + { + SelectListModel slModel = (SelectListModel) model; + JList list = new JList(slModel); + int size = HTML.getIntegerAttributeValue(atts, HTML.Attribute.SIZE, + 1); + list.setVisibleRowCount(size); + list.setSelectionModel(slModel.getSelectionModel()); + comp = new JScrollPane(list); + } + else if (model instanceof SelectComboBoxModel) + { + SelectComboBoxModel scbModel = (SelectComboBoxModel) model; + comp = new JComboBox(scbModel); + } + maxIsPreferred = true; } - // FIXME: Implement the remaining components. return comp; } @@ -188,16 +560,11 @@ public class FormView */ public float getMaximumSpan(int axis) { - // FIXME: The specs say that for some components the maximum span == the - // preferred span of the component. This should be figured out and - // implemented accordingly. float span; - if (axis == X_AXIS) - span = getComponent().getMaximumSize().width; - else if (axis == Y_AXIS) - span = getComponent().getMaximumSize().height; + if (maxIsPreferred) + span = getPreferredSpan(axis); else - throw new IllegalArgumentException("Invalid axis parameter"); + span = super.getMaximumSpan(axis); return span; } @@ -222,7 +589,9 @@ public class FormView AttributeSet atts = el.getAttributes(); String type = (String) atts.getAttribute(HTML.Attribute.TYPE); if (type.equals("submit")) - submitData(""); // FIXME: How to fetch the actual form data? + submitData(getFormData()); + else if (type.equals("reset")) + resetForm(); } // FIXME: Implement the remaining actions. } @@ -235,7 +604,8 @@ public class FormView */ protected void submitData(String data) { - // FIXME: Implement this. + SubmitThread submitThread = new SubmitThread(data); + submitThread.start(); } /** @@ -272,4 +642,229 @@ public class FormView } return data; } + + /** + * Determines and returns the enclosing form element if there is any. + * + * This is package private to avoid accessor methods. + * + * @return the enclosing form element, or <code>null</code> if there is no + * enclosing form element + */ + Element getFormElement() + { + Element form = null; + Element el = getElement(); + while (el != null && form == null) + { + AttributeSet atts = el.getAttributes(); + if (atts.getAttribute(StyleConstants.NameAttribute) == HTML.Tag.FORM) + form = el; + else + el = el.getParentElement(); + } + return form; + } + + /** + * Determines the form data that is about to be submitted. + * + * @return the form data + */ + private String getFormData() + { + Element form = getFormElement(); + StringBuilder b = new StringBuilder(); + if (form != null) + { + ElementIterator i = new ElementIterator(form); + Element next; + while ((next = i.next()) != null) + { + if (next.isLeaf()) + { + AttributeSet atts = next.getAttributes(); + String type = (String) atts.getAttribute(HTML.Attribute.TYPE); + if (type != null && type.equals("submit") + && next != getElement()) + { + // Skip this. This is not the actual submit trigger. + } + else if (type == null || ! type.equals("image")) + { + getElementFormData(next, b); + } + } + } + } + return b.toString(); + } + + /** + * Fetches the form data from the specified element and appends it to + * the data string. + * + * @param el the element from which to fetch form data + * @param b the data string + */ + private void getElementFormData(Element el, StringBuilder b) + { + AttributeSet atts = el.getAttributes(); + String name = (String) atts.getAttribute(HTML.Attribute.NAME); + if (name != null) + { + String value = null; + HTML.Tag tag = (HTML.Tag) atts.getAttribute(StyleConstants.NameAttribute); + if (tag == HTML.Tag.SELECT) + { + getSelectData(atts, b); + } + else + { + if (tag == HTML.Tag.INPUT) + value = getInputFormData(atts); + else if (tag == HTML.Tag.TEXTAREA) + value = getTextAreaData(atts); + if (name != null && value != null) + { + addData(b, name, value); + } + } + } + } + + /** + * Fetches form data from select boxes. + * + * @param atts the attributes of the element + * + * @param b the form data string to append to + */ + private void getSelectData(AttributeSet atts, StringBuilder b) + { + String name = (String) atts.getAttribute(HTML.Attribute.NAME); + if (name != null) + { + Object m = atts.getAttribute(StyleConstants.ModelAttribute); + if (m instanceof SelectListModel) + { + SelectListModel sl = (SelectListModel) m; + ListSelectionModel lsm = sl.getSelectionModel(); + for (int i = 0; i < sl.getSize(); i++) + { + if (lsm.isSelectedIndex(i)) + { + Option o = (Option) sl.getElementAt(i); + addData(b, name, o.getValue()); + } + } + } + else if (m instanceof SelectComboBoxModel) + { + SelectComboBoxModel scb = (SelectComboBoxModel) m; + Option o = (Option) scb.getSelectedItem(); + if (o != null) + addData(b, name, o.getValue()); + } + } + } + + /** + * Fetches form data from a textarea. + * + * @param atts the attributes + * + * @return the form data + */ + private String getTextAreaData(AttributeSet atts) + { + Document doc = (Document) atts.getAttribute(StyleConstants.ModelAttribute); + String data; + try + { + data = doc.getText(0, doc.getLength()); + } + catch (BadLocationException ex) + { + data = null; + } + return data; + } + + /** + * Fetches form data from an input tag. + * + * @param atts the attributes from which to fetch the data + * + * @return the field value + */ + private String getInputFormData(AttributeSet atts) + { + String type = (String) atts.getAttribute(HTML.Attribute.TYPE); + Object model = atts.getAttribute(StyleConstants.ModelAttribute); + String value = null; + if (type.equals("text") || type.equals("password")) + { + Document doc = (Document) model; + try + { + value = doc.getText(0, doc.getLength()); + } + catch (BadLocationException ex) + { + // Sigh. + assert false; + } + } + else if (type.equals("hidden") || type.equals("submit")) + { + value = (String) atts.getAttribute(HTML.Attribute.VALUE); + if (value == null) + value = ""; + } + // TODO: Implement the others. radio, checkbox and file. + return value; + } + + /** + * Actually adds the specified data to the string. It URL encodes + * the name and value and handles separation of the fields. + * + * @param b the string at which the form data to be added + * @param name the name of the field + * @param value the value + */ + private void addData(StringBuilder b, String name, String value) + { + if (b.length() > 0) + b.append('&'); + String encName = URLEncoder.encode(name); + b.append(encName); + b.append('='); + String encValue = URLEncoder.encode(value); + b.append(encValue); + } + + /** + * Resets the form data to their initial state. + */ + private void resetForm() + { + Element form = getFormElement(); + if (form != null) + { + ElementIterator iter = new ElementIterator(form); + Element next; + while ((next = iter.next()) != null) + { + if (next.isLeaf()) + { + AttributeSet atts = next.getAttributes(); + Object m = atts.getAttribute(StyleConstants.ModelAttribute); + if (m instanceof ResetableModel) + ((ResetableModel) m).reset(); + } + } + } + } } diff --git a/libjava/classpath/javax/swing/text/html/FrameSetView.java b/libjava/classpath/javax/swing/text/html/FrameSetView.java new file mode 100644 index 00000000000..e3252d79caf --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/FrameSetView.java @@ -0,0 +1,274 @@ +/* FrameSetView.java -- Implements HTML frameset + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import java.util.StringTokenizer; + +import javax.swing.text.AttributeSet; +import javax.swing.text.BoxView; +import javax.swing.text.Element; +import javax.swing.text.View; +import javax.swing.text.ViewFactory; + +/** + * Implements HTML framesets. This is implemented as a vertical box that + * holds the rows of the frameset. Each row is again a horizontal box that + * holds the actual columns. + */ +public class FrameSetView + extends BoxView +{ + + /** + * A row of a frameset. + */ + private class FrameSetRow + extends BoxView + { + private int row; + FrameSetRow(Element el, int r) + { + super(el, X_AXIS); + row = r; + } + + protected void loadChildren(ViewFactory f) + { + // Load the columns here. + Element el = getElement(); + View[] columns = new View[numViews[X_AXIS]]; + int offset = row * numViews[X_AXIS]; + for (int c = 0; c < numViews[X_AXIS]; c++) + { + Element child = el.getElement(offset + c); + columns[c] = f.create(child); + } + replace(0, 0, columns); + } + + protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, + int[] spans) + { + int numRows = numViews[X_AXIS]; + int[] abs = absolute[X_AXIS]; + int[] rel = relative[X_AXIS]; + int[] perc = percent[X_AXIS]; + layoutViews(targetSpan, axis, offsets, spans, numRows, abs, rel, perc); + } + } + + /** + * Holds the absolute layout information for the views along one axis. The + * indices are absolute[axis][index], where axis is either X_AXIS (columns) + * or Y_AXIS (rows). Rows or columns that don't have absolute layout have + * a -1 in this array. + */ + int[][] absolute; + + /** + * Holds the relative (*) layout information for the views along one axis. + * The indices are relative[axis][index], where axis is either X_AXIS + * (columns) or Y_AXIS (rows). Rows or columns that don't have relative + * layout have a Float.NaN in this array. + */ + int[][] relative; + + /** + * Holds the relative (%) layout information for the views along one axis. + * The indices are relative[axis][index], where axis is either X_AXIS + * (columns) or Y_AXIS (rows). Rows or columns that don't have relative + * layout have a Float.NaN in this array. + * + * The percentage is divided by 100 so that we hold the actual fraction here. + */ + int[][] percent; + + /** + * The number of children in each direction. + */ + int[] numViews; + + FrameSetView(Element el) + { + super(el, Y_AXIS); + numViews = new int[2]; + absolute = new int[2][]; + relative = new int[2][]; + percent = new int[2][]; + } + + /** + * Loads the children and places them inside the grid. + */ + protected void loadChildren(ViewFactory f) + { + parseRowsCols(); + // Set up the rows. + View[] rows = new View[numViews[Y_AXIS]]; + for (int r = 0; r < numViews[Y_AXIS]; r++) + { + rows[r] = new FrameSetRow(getElement(), r); + } + replace(0, 0, rows); + } + + /** + * Parses the rows and cols attributes and sets up the layout info. + */ + private void parseRowsCols() + { + Element el = getElement(); + AttributeSet atts = el.getAttributes(); + String cols = (String) atts.getAttribute(HTML.Attribute.COLS); + if (cols == null) // Defaults to '100%' when not specified. + cols = "100%"; + parseLayout(cols, X_AXIS); + String rows = (String) atts.getAttribute(HTML.Attribute.ROWS); + if (rows == null) // Defaults to '100%' when not specified. + rows = "100%"; + parseLayout(rows, Y_AXIS); + } + + /** + * Parses the cols or rows attribute and places the layout info in the + * appropriate arrays. + * + * @param att the attributes to parse + * @param axis the axis + */ + private void parseLayout(String att, int axis) + { + StringTokenizer tokens = new StringTokenizer(att, ","); + numViews[axis] = tokens.countTokens(); + absolute[axis] = new int[numViews[axis]]; + relative[axis] = new int[numViews[axis]]; + percent[axis] = new int[numViews[axis]]; + for (int index = 0; tokens.hasMoreTokens(); index++) + { + String token = tokens.nextToken(); + int p = token.indexOf('%'); + int s = token.indexOf('*'); + if (p != -1) + { + // Percent value. + String number = token.substring(0, p); + try + { + percent[axis][index] = Integer.parseInt(number); + } + catch (NumberFormatException ex) + { + // Leave value as 0 then. + } + } + else if (s != -1) + { + // Star relative value. + String number = token.substring(0, s); + try + { + relative[axis][index] = Integer.parseInt(number); + } + catch (NumberFormatException ex) + { + // Leave value as 0 then. + } + } + else + { + // Absolute value. + try + { + absolute[axis][index] = Integer.parseInt(token); + } + catch (NumberFormatException ex) + { + // Leave value as 0 then. + } + } + } + } + + protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, + int[] spans) + { + int numRows = numViews[Y_AXIS]; + int[] abs = absolute[Y_AXIS]; + int[] rel = relative[Y_AXIS]; + int[] perc = percent[Y_AXIS]; + layoutViews(targetSpan, axis, offsets, spans, numRows, abs, rel, perc); + } + + void layoutViews(int targetSpan, int axis, int[] offsets, int[] spans, + int numViews, int[] abs, int[] rel, int[] perc) + { + // We need two passes. In the first pass we layout the absolute and + // percent values and accumulate the needed space. In the second pass + // the relative values are distributed and the offsets are set. + int total = 0; + int relTotal = 0; + for (int i = 0; i < numViews; i++) + { + if (abs[i] > 0) + { + spans[i] = abs[i]; + total += spans[i]; + } + else if (perc[i] > 0) + { + spans[i] = (targetSpan * perc[i]) / 100; + total += spans[i]; + } + else if (rel[i] > 0) + { + relTotal += rel[i]; + } + } + int offs = 0; + for (int i = 0; i < numViews; i++) + { + if (relTotal > 0 && rel[i] > 0) + { + spans[i] = targetSpan * (rel[i] / relTotal); + } + offsets[i] = offs; + offs += spans[i]; + } + } +} diff --git a/libjava/classpath/javax/swing/text/html/FrameView.java b/libjava/classpath/javax/swing/text/html/FrameView.java new file mode 100644 index 00000000000..cd4e44a98ce --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/FrameView.java @@ -0,0 +1,233 @@ +/* FrameView.java -- Renders HTML frame tags + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import java.awt.Component; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + +import javax.swing.JEditorPane; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; +import javax.swing.text.AttributeSet; +import javax.swing.text.ComponentView; +import javax.swing.text.Element; +import javax.swing.text.View; + +/** + * A view that is responsible for rendering HTML frame tags. + * This is accomplished by a specialized {@link ComponentView} + * that embeds a JEditorPane with an own document. + */ +class FrameView + extends ComponentView + implements HyperlinkListener +{ + + /** + * Creates a new FrameView for the specified element. + * + * @param el the element for the view + */ + FrameView(Element el) + { + super(el); + } + + /** + * Creates the element that will be embedded in the view. + * This will be a JEditorPane with the appropriate content set. + * + * @return the element that will be embedded in the view + */ + protected Component createComponent() + { + Element el = getElement(); + AttributeSet atts = el.getAttributes(); + JEditorPane html = new JEditorPane(); + html.addHyperlinkListener(this); + URL base = ((HTMLDocument) el.getDocument()).getBase(); + String srcAtt = (String) atts.getAttribute(HTML.Attribute.SRC); + if (srcAtt != null && ! srcAtt.equals("")) + { + try + { + URL page = new URL(base, srcAtt); + html.setPage(page); + ((HTMLDocument) html.getDocument()).setFrameDocument(true); + } + catch (MalformedURLException ex) + { + // Leave page empty. + } + catch (IOException ex) + { + // Leave page empty. + } + } + return html; + } + + /** + * Catches hyperlink events on that frame's editor and forwards it to + * the outermost editorpane. + */ + public void hyperlinkUpdate(HyperlinkEvent event) + { + JEditorPane outer = getTopEditorPane(); + if (outer != null) + { + if (event instanceof HTMLFrameHyperlinkEvent) + { + HTMLFrameHyperlinkEvent hfhe = (HTMLFrameHyperlinkEvent) event; + if (hfhe.getEventType() == HyperlinkEvent.EventType.ACTIVATED) + { + String target = hfhe.getTarget(); + if (event instanceof FormSubmitEvent) + { + handleFormSubmitEvent(hfhe, outer, target); + } + else // No FormSubmitEvent. + { + handleHyperlinkEvent(hfhe, outer, target); + } + } + } + else + { + // Simply forward this event. + outer.fireHyperlinkUpdate(event); + } + } + } + + /** + * Handles normal hyperlink events. + * + * @param event the event + * @param outer the top editor + * @param target the target + */ + private void handleHyperlinkEvent(HyperlinkEvent event, + JEditorPane outer, String target) + { + if (target.equals("_top")) + { + try + { + outer.setPage(event.getURL()); + } + catch (IOException ex) + { + // Well... + ex.printStackTrace(); + } + } + if (! outer.isEditable()) + { + outer.fireHyperlinkUpdate + (new HTMLFrameHyperlinkEvent(outer, + event.getEventType(), + event.getURL(), + event.getDescription(), + getElement(), + target)); + } + } + + /** + * Handles form submit events. + * + * @param event the event + * @param outer the top editor + * @param target the target + */ + private void handleFormSubmitEvent(HTMLFrameHyperlinkEvent event, + JEditorPane outer, + String target) + { + HTMLEditorKit kit = (HTMLEditorKit) outer.getEditorKit(); + if (kit != null && kit.isAutoFormSubmission()) + { + if (target.equals("_top")) + { + try + { + outer.setPage(event.getURL()); + } + catch (IOException ex) + { + // Well... + ex.printStackTrace(); + } + } + else + { + HTMLDocument doc = + (HTMLDocument) outer.getDocument(); + doc.processHTMLFrameHyperlinkEvent(event); + } + } + else + { + outer.fireHyperlinkUpdate(event); + } + } + + /** + * Determines the topmost editor in a nested frameset. + * + * @return the topmost editor in a nested frameset + */ + private JEditorPane getTopEditorPane() + { + View parent = getParent(); + View top = null; + while (parent != null) + { + if (parent instanceof FrameSetView) + top = parent; + } + JEditorPane editor = null; + if (top != null) + editor = (JEditorPane) top.getContainer(); + return editor; + } +} diff --git a/libjava/classpath/javax/swing/text/html/HTML.java b/libjava/classpath/javax/swing/text/html/HTML.java index 2c908f6fc6e..93c05daa2f8 100644 --- a/libjava/classpath/javax/swing/text/html/HTML.java +++ b/libjava/classpath/javax/swing/text/html/HTML.java @@ -465,6 +465,16 @@ public class HTML public static final Attribute WIDTH = new Attribute("width"); /** + * This is used to reflect the pseudo class for the a tag. + */ + static final Attribute PSEUDO_CLASS = new Attribute("_pseudo"); + + /** + * This is used to reflect the dynamic class for the a tag. + */ + static final Attribute DYNAMIC_CLASS = new Attribute("_dynamic"); + + /** * The attribute name. */ private final String name; @@ -1119,8 +1129,8 @@ public class HTML static final int BLOCK = 2; static final int PREFORMATTED = 4; static final int SYNTHETIC = 8; - private static Map tagMap; - private static Map attrMap; + private static Map<String,Tag> tagMap; + private static Map<String,Attribute> attrMap; /** * The public constructor (does nothing). It it seldom required to have @@ -1159,7 +1169,7 @@ public class HTML if (attrMap == null) { // Create the map on demand. - attrMap = new TreeMap(); + attrMap = new TreeMap<String,Attribute>(); Attribute[] attrs = getAllAttributeKeys(); @@ -1169,7 +1179,7 @@ public class HTML } } - return (Attribute) attrMap.get(attName.toLowerCase()); + return attrMap.get(attName.toLowerCase()); } /** @@ -1228,7 +1238,7 @@ public class HTML if (tagMap == null) { // Create the mao on demand. - tagMap = new TreeMap(); + tagMap = new TreeMap<String,Tag>(); Tag[] tags = getAllTags(); @@ -1238,6 +1248,6 @@ public class HTML } } - return (Tag) tagMap.get(tagName.toLowerCase()); + return tagMap.get(tagName.toLowerCase()); } } diff --git a/libjava/classpath/javax/swing/text/html/HTMLDocument.java b/libjava/classpath/javax/swing/text/html/HTMLDocument.java index 0bfc338df45..f3d3ce3faaf 100644 --- a/libjava/classpath/javax/swing/text/html/HTMLDocument.java +++ b/libjava/classpath/javax/swing/text/html/HTMLDocument.java @@ -39,19 +39,22 @@ exception statement from your version. */ package javax.swing.text.html; import gnu.classpath.NotImplementedException; -import gnu.javax.swing.text.html.CharacterAttributeTranslator; -import gnu.javax.swing.text.html.parser.htmlAttributeSet; import java.io.IOException; import java.io.StringReader; +import java.net.MalformedURLException; import java.net.URL; +import java.util.ArrayList; import java.util.HashMap; import java.util.Stack; import java.util.Vector; +import javax.swing.ButtonGroup; +import javax.swing.DefaultButtonModel; import javax.swing.JEditorPane; +import javax.swing.ListSelectionModel; import javax.swing.event.DocumentEvent; -import javax.swing.event.HyperlinkEvent.EventType; +import javax.swing.event.UndoableEditEvent; import javax.swing.text.AbstractDocument; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; @@ -60,6 +63,7 @@ import javax.swing.text.Element; import javax.swing.text.ElementIterator; import javax.swing.text.GapContent; import javax.swing.text.MutableAttributeSet; +import javax.swing.text.PlainDocument; import javax.swing.text.SimpleAttributeSet; import javax.swing.text.StyleConstants; import javax.swing.text.html.HTML.Tag; @@ -87,16 +91,24 @@ public class HTMLDocument extends DefaultStyledDocument boolean preservesUnknownTags = true; int tokenThreshold = Integer.MAX_VALUE; HTMLEditorKit.Parser parser; - StyleSheet styleSheet; - AbstractDocument.Content content; - + + /** + * Indicates whether this document is inside a frame or not. + */ + private boolean frameDocument; + + /** + * Package private to avoid accessor methods. + */ + String baseTarget; + /** * Constructs an HTML document using the default buffer size and a default * StyleSheet. */ public HTMLDocument() { - this(null); + this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleSheet()); } /** @@ -119,14 +131,7 @@ public class HTMLDocument extends DefaultStyledDocument */ public HTMLDocument(AbstractDocument.Content c, StyleSheet styles) { - this.content = c; - if (styles == null) - { - styles = new StyleSheet(); - styles.importStyleSheet(getClass().getResource(HTMLEditorKit. - DEFAULT_CSS)); - } - this.styleSheet = styles; + super(c, styles); } /** @@ -137,7 +142,7 @@ public class HTMLDocument extends DefaultStyledDocument */ public StyleSheet getStyleSheet() { - return styleSheet; + return (StyleSheet) getAttributeContext(); } /** @@ -191,8 +196,6 @@ public class HTMLDocument extends DefaultStyledDocument protected Element createLeafElement(Element parent, AttributeSet a, int p0, int p1) { - RunElement el = new RunElement(parent, a, p0, p1); - el.addAttribute(StyleConstants.NameAttribute, HTML.Tag.CONTENT); return new RunElement(parent, a, p0, p1); } @@ -269,7 +272,7 @@ public class HTMLDocument extends DefaultStyledDocument public void setBase(URL u) { baseURL = u; - styleSheet.setBase(u); + getStyleSheet().setBase(u); } /** @@ -374,11 +377,119 @@ public class HTMLDocument extends DefaultStyledDocument } public void processHTMLFrameHyperlinkEvent(HTMLFrameHyperlinkEvent event) - throws NotImplementedException { - // TODO: Implement this properly. + String target = event.getTarget(); + Element el = event.getSourceElement(); + URL url = event.getURL(); + if (target.equals("_self")) + { + updateFrame(el, url); + } + else if (target.equals("_parent")) + { + updateFrameSet(el.getParentElement(), url); + } + else + { + Element targetFrame = findFrame(target); + if (targetFrame != null) + updateFrame(targetFrame, url); + } } - + + /** + * Finds the named frame inside this document. + * + * @param target the name to look for + * + * @return the frame if there is a matching frame, <code>null</code> + * otherwise + */ + private Element findFrame(String target) + { + ElementIterator i = new ElementIterator(this); + Element next = null; + while ((next = i.next()) != null) + { + AttributeSet atts = next.getAttributes(); + if (atts.getAttribute(StyleConstants.NameAttribute) == HTML.Tag.FRAME) + { + String name = (String) atts.getAttribute(HTML.Attribute.NAME); + if (name != null && name.equals(target)) + break; + } + } + return next; + } + + /** + * Updates the frame that is represented by the specified element to + * refer to the specified URL. + * + * @param el the element + * @param url the new url + */ + private void updateFrame(Element el, URL url) + { + try + { + writeLock(); + DefaultDocumentEvent ev = + new DefaultDocumentEvent(el.getStartOffset(), 1, + DocumentEvent.EventType.CHANGE); + AttributeSet elAtts = el.getAttributes(); + AttributeSet copy = elAtts.copyAttributes(); + MutableAttributeSet matts = (MutableAttributeSet) elAtts; + ev.addEdit(new AttributeUndoableEdit(el, copy, false)); + matts.removeAttribute(HTML.Attribute.SRC); + matts.addAttribute(HTML.Attribute.SRC, url.toString()); + ev.end(); + fireChangedUpdate(ev); + fireUndoableEditUpdate(new UndoableEditEvent(this, ev)); + } + finally + { + writeUnlock(); + } + } + + /** + * Updates the frameset that is represented by the specified element + * to create a frame that refers to the specified URL. + * + * @param el the element + * @param url the url + */ + private void updateFrameSet(Element el, URL url) + { + int start = el.getStartOffset(); + int end = el.getEndOffset(); + + StringBuilder html = new StringBuilder(); + html.append("<frame"); + if (url != null) + { + html.append(" src=\""); + html.append(url.toString()); + html.append("\""); + } + html.append('>'); + if (getParser() == null) + setParser(new HTMLEditorKit().getParser()); + try + { + setOuterHTML(el, html.toString()); + } + catch (BadLocationException ex) + { + ex.printStackTrace(); + } + catch (IOException ex) + { + ex.printStackTrace(); + } + } + /** * Gets an iterator for the given HTML.Tag. * @param t the requested HTML.Tag @@ -461,6 +572,8 @@ public class HTMLDocument extends DefaultStyledDocument String name = null; if (tag != null) name = tag.toString(); + if (name == null) + name = super.getName(); return name; } } @@ -497,6 +610,8 @@ public class HTMLDocument extends DefaultStyledDocument String name = null; if (tag != null) name = tag.toString(); + if (name == null) + name = super.getName(); return name; } @@ -518,24 +633,33 @@ public class HTMLDocument extends DefaultStyledDocument * @author Anthony Balkissoon abalkiss at redhat dot com */ public class HTMLReader extends HTMLEditorKit.ParserCallback - { + { + /** + * The maximum token threshold. We don't grow it larger than this. + */ + private static final int MAX_THRESHOLD = 10000; + + /** + * The threshold growth factor. + */ + private static final int GROW_THRESHOLD = 5; + /** * Holds the current character attribute set * */ protected MutableAttributeSet charAttr = new SimpleAttributeSet(); - protected Vector parseBuffer = new Vector(); - + protected Vector<ElementSpec> parseBuffer = new Vector<ElementSpec>(); + + /** + * The parse stack. It holds the current element tree path. + */ + private Stack<HTML.Tag> parseStack = new Stack<HTML.Tag>(); + /** * A stack for character attribute sets * */ Stack charAttrStack = new Stack(); - - /** - * The parse stack. This stack holds HTML.Tag objects that reflect the - * current position in the parsing process. - */ - Stack parseStack = new Stack(); /** A mapping between HTML.Tag objects and the actions that handle them **/ HashMap tagToAction; @@ -571,13 +695,68 @@ public class HTMLDocument extends DefaultStyledDocument /** A temporary variable that helps with the printing out of debug information **/ boolean debug = false; - - void print (String line) - { - if (debug) - System.out.println (line); - } - + + /** + * This is true when we are inside a pre tag. + */ + boolean inPreTag = false; + + /** + * This is true when we are inside a style tag. This will add text + * content inside this style tag beeing parsed as CSS. + * + * This is package private to avoid accessor methods. + */ + boolean inStyleTag = false; + + /** + * This is true when we are inside a <textarea> tag. Any text + * content will then be added to the text area. + * + * This is package private to avoid accessor methods. + */ + boolean inTextArea = false; + + /** + * This contains all stylesheets that are somehow read, either + * via embedded style tags, or via linked stylesheets. The + * elements will be String objects containing a stylesheet each. + */ + ArrayList styles; + + /** + * The document model for a textarea. + * + * This is package private to avoid accessor methods. + */ + ResetablePlainDocument textAreaDocument; + + /** + * The current model of a select tag. Can be a ComboBoxModel or a + * ListModel depending on the type of the select box. + */ + Object selectModel; + + /** + * The current option beeing read. + */ + Option option; + + /** + * The current number of options in the current select model. + */ + int numOptions; + + /** + * The current button groups mappings. + */ + HashMap buttonGroups; + + /** + * The token threshold. This gets increased while loading. + */ + private int threshold; + public class TagAction { /** @@ -633,13 +812,12 @@ public class HTMLDocument extends DefaultStyledDocument // Put the old attribute set on the stack. pushCharacterStyle(); - // Translate tag.. return if succesful. - if(CharacterAttributeTranslator.translateTag(charAttr, t, a)) - return; + // Initialize with link pseudo class. + if (t == HTML.Tag.A) + a.addAttribute(HTML.Attribute.PSEUDO_CLASS, "link"); // Just add the attributes in <code>a</code>. - if (a != null) - charAttr.addAttribute(t, a.copyAttributes()); + charAttr.addAttribute(t, a.copyAttributes()); } /** @@ -651,7 +829,11 @@ public class HTMLDocument extends DefaultStyledDocument popCharacterStyle(); } } - + + /** + * Processes elements that make up forms: <input>, <textarea>, + * <select> and <option>. + */ public class FormAction extends SpecialAction { /** @@ -659,10 +841,73 @@ public class HTMLDocument extends DefaultStyledDocument * of tags associated with this Action. */ public void start(HTML.Tag t, MutableAttributeSet a) - throws NotImplementedException { - // FIXME: Implement. - print ("FormAction.start not implemented"); + if (t == HTML.Tag.INPUT) + { + String type = (String) a.getAttribute(HTML.Attribute.TYPE); + if (type == null) + { + type = "text"; // Default to 'text' when nothing was specified. + a.addAttribute(HTML.Attribute.TYPE, type); + } + setModel(type, a); + } + else if (t == HTML.Tag.TEXTAREA) + { + inTextArea = true; + textAreaDocument = new ResetablePlainDocument(); + a.addAttribute(StyleConstants.ModelAttribute, textAreaDocument); + } + else if (t == HTML.Tag.SELECT) + { + int size = HTML.getIntegerAttributeValue(a, HTML.Attribute.SIZE, + 1); + boolean multi = a.getAttribute(HTML.Attribute.MULTIPLE) != null; + if (size > 1 || multi) + { + SelectListModel m = new SelectListModel(); + if (multi) + m.getSelectionModel().setSelectionMode(ListSelectionModel + .MULTIPLE_INTERVAL_SELECTION); + selectModel = m; + } + else + { + selectModel = new SelectComboBoxModel(); + } + a.addAttribute(StyleConstants.ModelAttribute, selectModel); + } + if (t == HTML.Tag.OPTION) + { + option = new Option(a); + if (selectModel instanceof SelectListModel) + { + SelectListModel m = (SelectListModel) selectModel; + m.addElement(option); + if (option.isSelected()) + { + m.getSelectionModel().addSelectionInterval(numOptions, + numOptions); + m.addInitialSelection(numOptions); + } + } + else if (selectModel instanceof SelectComboBoxModel) + { + SelectComboBoxModel m = (SelectComboBoxModel) selectModel; + m.addElement(option); + if (option.isSelected()) + { + m.setSelectedItem(option); + m.setInitialSelection(option); + } + } + numOptions++; + } + else + { + // Build the element. + super.start(t, a); + } } /** @@ -670,13 +915,106 @@ public class HTMLDocument extends DefaultStyledDocument * with this Action. */ public void end(HTML.Tag t) - throws NotImplementedException { - // FIXME: Implement. - print ("FormAction.end not implemented"); + if (t == HTML.Tag.OPTION) + { + option = null; + } + else + { + if (t == HTML.Tag.TEXTAREA) + { + inTextArea = false; + } + else if (t == HTML.Tag.SELECT) + { + selectModel = null; + numOptions = 0; + } + // Finish the element. + super.end(t); + } + } + + private void setModel(String type, MutableAttributeSet attrs) + { + if (type.equals("submit") || type.equals("reset") + || type.equals("image")) + { + // Create button. + attrs.addAttribute(StyleConstants.ModelAttribute, + new DefaultButtonModel()); + } + else if (type.equals("text") || type.equals("password")) + { + String text = (String) attrs.getAttribute(HTML.Attribute.VALUE); + ResetablePlainDocument doc = new ResetablePlainDocument(); + if (text != null) + { + doc.setInitialText(text); + try + { + doc.insertString(0, text, null); + } + catch (BadLocationException ex) + { + // Shouldn't happen. + assert false; + } + } + attrs.addAttribute(StyleConstants.ModelAttribute, doc); + } + else if (type.equals("file")) + { + attrs.addAttribute(StyleConstants.ModelAttribute, + new PlainDocument()); + } + else if (type.equals("checkbox") || type.equals("radio")) + { + ResetableToggleButtonModel model = + new ResetableToggleButtonModel(); + if (attrs.getAttribute(HTML.Attribute.SELECTED) != null) + { + model.setSelected(true); + model.setInitial(true); + } + if (type.equals("radio")) + { + String name = (String) attrs.getAttribute(HTML.Attribute.NAME); + if (name != null) + { + if (buttonGroups == null) + buttonGroups = new HashMap(); + ButtonGroup group = (ButtonGroup) buttonGroups.get(name); + if (group == null) + { + group = new ButtonGroup(); + buttonGroups.put(name, group); + } + model.setGroup(group); + } + } + attrs.addAttribute(StyleConstants.ModelAttribute, model); + } + } + } + + /** + * Called for form tags. + */ + class FormTagAction + extends BlockAction + { + /** + * Clears the button group mapping. + */ + public void end(HTML.Tag t) + { + super.end(t); + buttonGroups = null; } } - + /** * This action indicates that the content between starting and closing HTML * elements (like script - /script) should not be visible. The content is @@ -707,7 +1045,10 @@ public class HTMLDocument extends DefaultStyledDocument blockClose(t); } } - + + /** + * Handles <isindex> tags. + */ public class IsindexAction extends TagAction { /** @@ -715,10 +1056,10 @@ public class HTMLDocument extends DefaultStyledDocument * of tags associated with this Action. */ public void start(HTML.Tag t, MutableAttributeSet a) - throws NotImplementedException { - // FIXME: Implement. - print ("IsindexAction.start not implemented"); + blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet()); + addSpecialElement(t, a); + blockClose(HTML.Tag.IMPLIED); } } @@ -730,7 +1071,7 @@ public class HTMLDocument extends DefaultStyledDocument */ public void start(HTML.Tag t, MutableAttributeSet a) { - blockOpen(t, a); + super.start(t, a); } /** @@ -739,10 +1080,13 @@ public class HTMLDocument extends DefaultStyledDocument */ public void end(HTML.Tag t) { - blockClose(t); + super.end(t); } } - + + /** + * This action is performed when a <pre> tag is parsed. + */ public class PreAction extends BlockAction { /** @@ -750,11 +1094,11 @@ public class HTMLDocument extends DefaultStyledDocument * of tags associated with this Action. */ public void start(HTML.Tag t, MutableAttributeSet a) - throws NotImplementedException { - // FIXME: Implement. - print ("PreAction.start not implemented"); - super.start(t, a); + inPreTag = true; + blockOpen(t, a); + a.addAttribute(CSS.Attribute.WHITE_SPACE, "pre"); + blockOpen(HTML.Tag.IMPLIED, a); } /** @@ -762,11 +1106,10 @@ public class HTMLDocument extends DefaultStyledDocument * with this Action. */ public void end(HTML.Tag t) - throws NotImplementedException { - // FIXME: Implement. - print ("PreAction.end not implemented"); - super.end(t); + blockClose(HTML.Tag.IMPLIED); + inPreTag = false; + blockClose(t); } } @@ -798,7 +1141,6 @@ public class HTMLDocument extends DefaultStyledDocument throws NotImplementedException { // FIXME: Implement. - print ("AreaAction.start not implemented"); } /** @@ -809,10 +1151,44 @@ public class HTMLDocument extends DefaultStyledDocument throws NotImplementedException { // FIXME: Implement. - print ("AreaAction.end not implemented"); } } - + + /** + * Converts HTML tags to CSS attributes. + */ + class ConvertAction + extends TagAction + { + + public void start(HTML.Tag tag, MutableAttributeSet atts) + { + pushCharacterStyle(); + charAttr.addAttribute(tag, atts.copyAttributes()); + StyleSheet styleSheet = getStyleSheet(); + // TODO: Add other tags here. + if (tag == HTML.Tag.FONT) + { + String color = (String) atts.getAttribute(HTML.Attribute.COLOR); + if (color != null) + styleSheet.addCSSAttribute(charAttr, CSS.Attribute.COLOR, color); + String face = (String) atts.getAttribute(HTML.Attribute.FACE); + if (face != null) + styleSheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_FAMILY, + face); + String size = (String) atts.getAttribute(HTML.Attribute.SIZE); + if (size != null) + styleSheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_SIZE, + size); + } + } + + public void end(HTML.Tag tag) + { + popCharacterStyle(); + } + } + class BaseAction extends TagAction { /** @@ -820,22 +1196,9 @@ public class HTMLDocument extends DefaultStyledDocument * of tags associated with this Action. */ public void start(HTML.Tag t, MutableAttributeSet a) - throws NotImplementedException { - // FIXME: Implement. - print ("BaseAction.start not implemented"); + baseTarget = (String) a.getAttribute(HTML.Attribute.TARGET); } - - /** - * Called when an end tag is seen for one of the types of tags associated - * with this Action. - */ - public void end(HTML.Tag t) - throws NotImplementedException - { - // FIXME: Implement. - print ("BaseAction.end not implemented"); - } } class HeadAction extends BlockAction @@ -848,7 +1211,6 @@ public class HTMLDocument extends DefaultStyledDocument throws NotImplementedException { // FIXME: Implement. - print ("HeadAction.start not implemented: "+t); super.start(t, a); } @@ -857,37 +1219,87 @@ public class HTMLDocument extends DefaultStyledDocument * with this Action. */ public void end(HTML.Tag t) - throws NotImplementedException { - // FIXME: Implement. - print ("HeadAction.end not implemented: "+t); + // We read in all the stylesheets that are embedded or referenced + // inside the header. + if (styles != null) + { + int numStyles = styles.size(); + for (int i = 0; i < numStyles; i++) + { + String style = (String) styles.get(i); + getStyleSheet().addRule(style); + } + } super.end(t); - } + } } - class LinkAction extends TagAction + class LinkAction extends HiddenAction { /** * This method is called when a start tag is seen for one of the types * of tags associated with this Action. */ public void start(HTML.Tag t, MutableAttributeSet a) - throws NotImplementedException { - // FIXME: Implement. - print ("LinkAction.start not implemented"); + super.start(t, a); + String type = (String) a.getAttribute(HTML.Attribute.TYPE); + if (type == null) + type = "text/css"; + if (type.equals("text/css")) + { + String rel = (String) a.getAttribute(HTML.Attribute.REL); + String media = (String) a.getAttribute(HTML.Attribute.MEDIA); + String title = (String) a.getAttribute(HTML.Attribute.TITLE); + if (media == null) + media = "all"; + else + media = media.toLowerCase(); + if (rel != null) + { + rel = rel.toLowerCase(); + if ((media.indexOf("all") != -1 + || media.indexOf("screen") != -1) + && (rel.equals("stylesheet"))) + { + String href = (String) a.getAttribute(HTML.Attribute.HREF); + URL url = null; + try + { + url = new URL(baseURL, href); + } + catch (MalformedURLException ex) + { + try + { + url = new URL(href); + } + catch (MalformedURLException ex2) + { + url = null; + } + } + if (url != null) + { + try + { + getStyleSheet().importStyleSheet(url); + } + catch (Exception ex) + { + // Don't let exceptions and runtime exceptions + // in CSS parsing disprupt the HTML parsing + // process. But inform the user/developer + // on the console about it. + ex.printStackTrace(); + } + } + } + } + } } - /** - * Called when an end tag is seen for one of the types of tags associated - * with this Action. - */ - public void end(HTML.Tag t) - throws NotImplementedException - { - // FIXME: Implement. - print ("LinkAction.end not implemented"); - } } class MapAction extends TagAction @@ -900,7 +1312,6 @@ public class HTMLDocument extends DefaultStyledDocument throws NotImplementedException { // FIXME: Implement. - print ("MapAction.start not implemented"); } /** @@ -911,7 +1322,6 @@ public class HTMLDocument extends DefaultStyledDocument throws NotImplementedException { // FIXME: Implement. - print ("MapAction.end not implemented"); } } @@ -925,7 +1335,6 @@ public class HTMLDocument extends DefaultStyledDocument throws NotImplementedException { // FIXME: Implement. - print ("MetaAction.start not implemented"); } /** @@ -936,10 +1345,9 @@ public class HTMLDocument extends DefaultStyledDocument throws NotImplementedException { // FIXME: Implement. - print ("MetaAction.end not implemented"); } } - + class StyleAction extends TagAction { /** @@ -947,10 +1355,8 @@ public class HTMLDocument extends DefaultStyledDocument * of tags associated with this Action. */ public void start(HTML.Tag t, MutableAttributeSet a) - throws NotImplementedException { - // FIXME: Implement. - print ("StyleAction.start not implemented"); + inStyleTag = true; } /** @@ -958,10 +1364,8 @@ public class HTMLDocument extends DefaultStyledDocument * with this Action. */ public void end(HTML.Tag t) - throws NotImplementedException { - // FIXME: Implement. - print ("StyleAction.end not implemented"); + inStyleTag = false; } } @@ -975,7 +1379,6 @@ public class HTMLDocument extends DefaultStyledDocument throws NotImplementedException { // FIXME: Implement. - print ("TitleAction.start not implemented"); } /** @@ -986,7 +1389,6 @@ public class HTMLDocument extends DefaultStyledDocument throws NotImplementedException { // FIXME: Implement. - print ("TitleAction.end not implemented"); } } @@ -998,13 +1400,11 @@ public class HTMLDocument extends DefaultStyledDocument public HTMLReader(int offset, int popDepth, int pushDepth, HTML.Tag insertTag) { - print ("HTMLReader created with pop: "+popDepth - + " push: "+pushDepth + " offset: "+offset - + " tag: "+insertTag); this.insertTag = insertTag; this.offset = offset; this.popDepth = popDepth; this.pushDepth = pushDepth; + threshold = getTokenThreshold(); initTags(); } @@ -1028,7 +1428,7 @@ public class HTMLDocument extends DefaultStyledDocument StyleAction styleAction = new StyleAction(); TitleAction titleAction = new TitleAction(); - + ConvertAction convertAction = new ConvertAction(); tagToAction.put(HTML.Tag.A, characterAction); tagToAction.put(HTML.Tag.ADDRESS, characterAction); tagToAction.put(HTML.Tag.APPLET, hiddenAction); @@ -1051,8 +1451,8 @@ public class HTMLDocument extends DefaultStyledDocument tagToAction.put(HTML.Tag.DL, blockAction); tagToAction.put(HTML.Tag.DT, paragraphAction); tagToAction.put(HTML.Tag.EM, characterAction); - tagToAction.put(HTML.Tag.FONT, characterAction); - tagToAction.put(HTML.Tag.FORM, blockAction); + tagToAction.put(HTML.Tag.FONT, convertAction); + tagToAction.put(HTML.Tag.FORM, new FormTagAction()); tagToAction.put(HTML.Tag.FRAME, specialAction); tagToAction.put(HTML.Tag.FRAMESET, blockAction); tagToAction.put(HTML.Tag.H1, paragraphAction); @@ -1142,18 +1542,28 @@ public class HTMLDocument extends DefaultStyledDocument */ public void flush() throws BadLocationException { - DefaultStyledDocument.ElementSpec[] elements; - elements = new DefaultStyledDocument.ElementSpec[parseBuffer.size()]; - parseBuffer.copyInto(elements); - parseBuffer.removeAllElements(); - if (offset == 0) - create(elements); - else - insert(offset, elements); + flushImpl(); + } - offset += HTMLDocument.this.getLength() - offset; + /** + * Flushes the buffer and handle partial inserts. + * + */ + private void flushImpl() + throws BadLocationException + { + int oldLen = getLength(); + int size = parseBuffer.size(); + ElementSpec[] elems = new ElementSpec[size]; + parseBuffer.copyInto(elems); + if (oldLen == 0) + create(elems); + else + insert(offset, elems); + parseBuffer.removeAllElements(); + offset += getLength() - oldLen; } - + /** * This method is called by the parser to indicate a block of * text was encountered. Should insert the text appropriately. @@ -1163,8 +1573,24 @@ public class HTMLDocument extends DefaultStyledDocument */ public void handleText(char[] data, int pos) { - if (data != null && data.length > 0) - addContent(data, 0, data.length); + if (shouldInsert() && data != null && data.length > 0) + { + if (inTextArea) + textAreaContent(data); + else if (inPreTag) + preContent(data); + else if (option != null) + option.setLabel(new String(data)); + else if (inStyleTag) + { + if (styles == null) + styles = new ArrayList(); + styles.add(new String(data)); + } + else + addContent(data, 0, data.length); + + } } /** @@ -1214,8 +1640,7 @@ public class HTMLDocument extends DefaultStyledDocument TagAction action = (TagAction) tagToAction.get(HTML.Tag.COMMENT); if (action != null) { - action.start(HTML.Tag.COMMENT, - htmlAttributeSet.EMPTY_HTML_ATTRIBUTE_SET); + action.start(HTML.Tag.COMMENT, new SimpleAttributeSet()); action.end(HTML.Tag.COMMENT); } } @@ -1277,7 +1702,6 @@ public class HTMLDocument extends DefaultStyledDocument public void handleEndOfLineString(String eol) { // FIXME: Implement. - print ("HTMLReader.handleEndOfLineString not implemented yet"); } /** @@ -1287,22 +1711,48 @@ public class HTMLDocument extends DefaultStyledDocument * @param data the text to add to the textarea */ protected void textAreaContent(char[] data) - throws NotImplementedException { - // FIXME: Implement. - print ("HTMLReader.textAreaContent not implemented yet"); + try + { + int offset = textAreaDocument.getLength(); + String text = new String(data); + textAreaDocument.setInitialText(text); + textAreaDocument.insertString(offset, text, null); + } + catch (BadLocationException ex) + { + // Must not happen as we insert at a model location that we + // got from the document itself. + assert false; + } } /** * Adds the given text that was encountered in a <PRE> element. - * + * This adds synthesized lines to hold the text runs. + * * @param data the text */ protected void preContent(char[] data) - throws NotImplementedException { - // FIXME: Implement - print ("HTMLReader.preContent not implemented yet"); + int start = 0; + for (int i = 0; i < data.length; i++) + { + if (data[i] == '\n') + { + addContent(data, start, i - start + 1); + blockClose(HTML.Tag.IMPLIED); + MutableAttributeSet atts = new SimpleAttributeSet(); + atts.addAttribute(CSS.Attribute.WHITE_SPACE, "pre"); + blockOpen(HTML.Tag.IMPLIED, atts); + start = i + 1; + } + } + if (start < data.length) + { + // Add remaining last line. + addContent(data, start, data.length - start); + } } /** @@ -1314,17 +1764,48 @@ public class HTMLDocument extends DefaultStyledDocument */ protected void blockOpen(HTML.Tag t, MutableAttributeSet attr) { - printBuffer(); - DefaultStyledDocument.ElementSpec element; + if (inImpliedParagraph()) + blockClose(HTML.Tag.IMPLIED); + // Push the new tag on top of the stack. parseStack.push(t); + + DefaultStyledDocument.ElementSpec element; + AbstractDocument.AttributeContext ctx = getAttributeContext(); AttributeSet copy = attr.copyAttributes(); copy = ctx.addAttribute(copy, StyleConstants.NameAttribute, t); element = new DefaultStyledDocument.ElementSpec(copy, DefaultStyledDocument.ElementSpec.StartTagType); parseBuffer.addElement(element); - printBuffer(); + } + + /** + * Returns true when we are currently inside a paragraph, either + * a real one or an implied, false otherwise. + * + * @return + */ + private boolean inParagraph() + { + boolean inParagraph = false; + if (! parseStack.isEmpty()) + { + HTML.Tag top = parseStack.peek(); + inParagraph = top == HTML.Tag.P || top == HTML.Tag.IMPLIED; + } + return inParagraph; + } + + private boolean inImpliedParagraph() + { + boolean inParagraph = false; + if (! parseStack.isEmpty()) + { + HTML.Tag top = parseStack.peek(); + inParagraph = top == HTML.Tag.IMPLIED; + } + return inParagraph; } /** @@ -1335,32 +1816,29 @@ public class HTMLDocument extends DefaultStyledDocument */ protected void blockClose(HTML.Tag t) { - printBuffer(); DefaultStyledDocument.ElementSpec element; + if (inImpliedParagraph() && t != HTML.Tag.IMPLIED) + blockClose(HTML.Tag.IMPLIED); + + // Pull the token from the stack. + if (! parseStack.isEmpty()) // Just to be sure. + parseStack.pop(); + // If the previous tag is a start tag then we insert a synthetic // content tag. DefaultStyledDocument.ElementSpec prev; - prev = (DefaultStyledDocument.ElementSpec) - parseBuffer.get(parseBuffer.size() - 1); - if (prev.getType() == DefaultStyledDocument.ElementSpec.StartTagType) + prev = parseBuffer.size() > 0 ? (DefaultStyledDocument.ElementSpec) + parseBuffer.get(parseBuffer.size() - 1) : null; + if (prev != null && + prev.getType() == DefaultStyledDocument.ElementSpec.StartTagType) { - AbstractDocument.AttributeContext ctx = getAttributeContext(); - AttributeSet attributes = ctx.getEmptySet(); - attributes = ctx.addAttribute(attributes, StyleConstants.NameAttribute, - HTML.Tag.CONTENT); - element = new DefaultStyledDocument.ElementSpec(attributes, - DefaultStyledDocument.ElementSpec.ContentType, - new char[0], 0, 0); - parseBuffer.add(element); + addContent(new char[]{' '}, 0, 1); } element = new DefaultStyledDocument.ElementSpec(null, DefaultStyledDocument.ElementSpec.EndTagType); parseBuffer.addElement(element); - printBuffer(); - if (parseStack.size() > 0) - parseStack.pop(); } /** @@ -1389,6 +1867,11 @@ public class HTMLDocument extends DefaultStyledDocument protected void addContent(char[] data, int offs, int length, boolean generateImpliedPIfNecessary) { + if (generateImpliedPIfNecessary && ! inParagraph()) + { + blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet()); + } + AbstractDocument.AttributeContext ctx = getAttributeContext(); DefaultStyledDocument.ElementSpec element; AttributeSet attributes = null; @@ -1405,16 +1888,16 @@ public class HTMLDocument extends DefaultStyledDocument DefaultStyledDocument.ElementSpec.ContentType, data, offs, length); - printBuffer(); // Add the element to the buffer parseBuffer.addElement(element); - printBuffer(); - if (parseBuffer.size() > HTMLDocument.this.getTokenThreshold()) + if (parseBuffer.size() > threshold) { + if (threshold <= MAX_THRESHOLD) + threshold *= GROW_THRESHOLD; try { - flush(); + flushImpl(); } catch (BadLocationException ble) { @@ -1431,29 +1914,23 @@ public class HTMLDocument extends DefaultStyledDocument */ protected void addSpecialElement(HTML.Tag t, MutableAttributeSet a) { + if (t != HTML.Tag.FRAME && ! inParagraph()) + { + blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet()); + } + a.addAttribute(StyleConstants.NameAttribute, t); - // Migrate from the rather htmlAttributeSet to the faster, lighter and - // unchangeable alternative implementation. - AttributeSet copy = a.copyAttributes(); - // The two spaces are required because some special elements like HR // must be broken. At least two characters are needed to break into the // two parts. DefaultStyledDocument.ElementSpec spec = - new DefaultStyledDocument.ElementSpec(copy, + new DefaultStyledDocument.ElementSpec(a.copyAttributes(), DefaultStyledDocument.ElementSpec.ContentType, - new char[] {' ', ' '}, 0, 2 ); + new char[] {' '}, 0, 1 ); parseBuffer.add(spec); } - void printBuffer() - { - print ("\n*********BUFFER**********"); - for (int i = 0; i < parseBuffer.size(); i ++) - print (" "+parseBuffer.get(i)); - print ("***************************"); - } } /** @@ -1533,10 +2010,6 @@ public class HTMLDocument extends DefaultStyledDocument } }; - // Set the parent HTML tag. - reader.parseStack.push(parent.getAttributes().getAttribute( - StyleConstants.NameAttribute)); - return reader; } @@ -1728,4 +2201,98 @@ public void setOuterHTML(Element elem, String htmlText) // TODO charset getParser().parse(new StringReader(htmlText), reader, true); } + + /** + * Overridden to tag content with the synthetic HTML.Tag.CONTENT + * tag. + */ + protected void insertUpdate(DefaultDocumentEvent evt, AttributeSet att) + { + if (att == null) + { + SimpleAttributeSet sas = new SimpleAttributeSet(); + sas.addAttribute(StyleConstants.NameAttribute, HTML.Tag.CONTENT); + att = sas; + } + super.insertUpdate(evt, att); + } + + /** + * Returns <code>true</code> when this document is inside a frame, + * <code>false</code> otherwise. + * + * @return <code>true</code> when this document is inside a frame, + * <code>false</code> otherwise + */ + boolean isFrameDocument() + { + return frameDocument; + } + + /** + * Set <code>true</code> when this document is inside a frame, + * <code>false</code> otherwise. + * + * @param frameDoc <code>true</code> when this document is inside a frame, + * <code>false</code> otherwise + */ + void setFrameDocument(boolean frameDoc) + { + frameDocument = frameDoc; + } + + /** + * Returns the target that is specified in the base tag, if this is the case. + * + * @return the target that is specified in the base tag, if this is the case + */ + String getBaseTarget() + { + return baseTarget; + } + + /** + * Updates the A tag's pseudo class value in response to a hyperlink + * action. + * + * @param el the corresponding element + * @param value the new value + */ + void updateSpecialClass(Element el, HTML.Attribute cl, String value) + { + try + { + writeLock(); + DefaultDocumentEvent ev = + new DefaultDocumentEvent(el.getStartOffset(), 1, + DocumentEvent.EventType.CHANGE); + AttributeSet elAtts = el.getAttributes(); + AttributeSet anchorAtts = (AttributeSet) elAtts.getAttribute(HTML.Tag.A); + if (anchorAtts != null) + { + AttributeSet copy = elAtts.copyAttributes(); + StyleSheet ss = getStyleSheet(); + if (value != null) + { + anchorAtts = ss.addAttribute(anchorAtts, cl, value); + } + else + { + anchorAtts = ss.removeAttribute(anchorAtts, cl); + } + MutableAttributeSet matts = (MutableAttributeSet) elAtts; + ev.addEdit(new AttributeUndoableEdit(el, copy, false)); + matts.removeAttribute(HTML.Tag.A); + matts.addAttribute(HTML.Tag.A, anchorAtts); + ev.end(); + fireChangedUpdate(ev); + fireUndoableEditUpdate(new UndoableEditEvent(this, ev)); + } + } + finally + { + writeUnlock(); + } + } + } diff --git a/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java b/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java index 5d77be8fdd4..0ede1c74ed9 100644 --- a/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java +++ b/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java @@ -39,32 +39,38 @@ exception statement from your version. */ package javax.swing.text.html; -import gnu.classpath.NotImplementedException; - import java.awt.event.ActionEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; import java.awt.Cursor; +import java.awt.Point; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.io.Reader; import java.io.Serializable; import java.io.StringReader; import java.io.Writer; +import java.net.MalformedURLException; +import java.net.URL; import javax.accessibility.Accessible; import javax.accessibility.AccessibleContext; import javax.swing.Action; import javax.swing.JEditorPane; +import javax.swing.SwingUtilities; +import javax.swing.event.HyperlinkEvent; +import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.EditorKit; import javax.swing.text.Element; import javax.swing.text.MutableAttributeSet; import javax.swing.text.StyleConstants; -import javax.swing.text.StyleContext; +import javax.swing.text.StyledDocument; import javax.swing.text.StyledEditorKit; import javax.swing.text.TextAction; import javax.swing.text.View; @@ -74,7 +80,7 @@ import javax.swing.text.html.parser.ParserDelegator; /* Move these imports here after javax.swing.text.html to make it compile with jikes. */ import gnu.javax.swing.text.html.parser.GnuParserDelegator; -import gnu.javax.swing.text.html.parser.HTML_401Swing; +import gnu.javax.swing.text.html.parser.HTML_401F; /** * @author Lillian Angel (langel at redhat dot com) @@ -92,7 +98,12 @@ public class HTMLEditorKit extends MouseAdapter implements MouseMotionListener, Serializable { - + + /** + * The element of the last anchor tag. + */ + private Element lastAnchorElement; + /** * Constructor */ @@ -110,11 +121,14 @@ public class HTMLEditorKit */ public void mouseClicked(MouseEvent e) { - /* - These MouseInputAdapter methods generate mouse appropriate events around - hyperlinks (entering, exiting, and activating). - */ - // FIXME: Not implemented. + JEditorPane editor = (JEditorPane) e.getSource(); + if (! editor.isEditable() && SwingUtilities.isLeftMouseButton(e)) + { + Point loc = e.getPoint(); + int pos = editor.viewToModel(loc); + if (pos >= 0) + activateLink(pos, editor, e.getX(), e.getY()); + } } /** @@ -124,11 +138,7 @@ public class HTMLEditorKit */ public void mouseDragged(MouseEvent e) { - /* - These MouseInputAdapter methods generate mouse appropriate events around - hyperlinks (entering, exiting, and activating). - */ - // FIXME: Not implemented. + // Nothing to do here. } /** @@ -138,29 +148,159 @@ public class HTMLEditorKit */ public void mouseMoved(MouseEvent e) { - /* - These MouseInputAdapter methods generate mouse appropriate events around - hyperlinks (entering, exiting, and activating). - */ - // FIXME: Not implemented. + JEditorPane editor = (JEditorPane) e.getSource(); + HTMLEditorKit kit = (HTMLEditorKit) editor.getEditorKit(); + if (! editor.isEditable()) + { + Document doc = editor.getDocument(); + if (doc instanceof HTMLDocument) + { + Cursor newCursor = kit.getDefaultCursor(); + HTMLDocument htmlDoc = (HTMLDocument) doc; + Point loc = e.getPoint(); + int pos = editor.viewToModel(loc); + Element el = htmlDoc.getCharacterElement(pos); + if (pos < el.getStartOffset() || pos >= el.getEndOffset()) + el = null; + if (el != null) + { + AttributeSet aAtts = (AttributeSet) + el.getAttributes().getAttribute(HTML.Tag.A); + if (aAtts != null) + { + if (el != lastAnchorElement) + { + if (lastAnchorElement != null) + htmlDoc.updateSpecialClass(lastAnchorElement, + HTML.Attribute.DYNAMIC_CLASS, + null); + lastAnchorElement = el; + htmlDoc.updateSpecialClass(el, + HTML.Attribute.DYNAMIC_CLASS, + "hover"); + } + newCursor = kit.getLinkCursor(); + } + else + { + if (lastAnchorElement != null) + htmlDoc.updateSpecialClass(lastAnchorElement, + HTML.Attribute.DYNAMIC_CLASS, + null); + lastAnchorElement = null; + } + } + else + { + if (lastAnchorElement != null) + htmlDoc.updateSpecialClass(lastAnchorElement, + HTML.Attribute.DYNAMIC_CLASS, + null); + lastAnchorElement = null; + } + if (editor.getCursor() != newCursor) + { + editor.setCursor(newCursor); + } + } + } } - + /** * If the given position represents a link, then linkActivated is called - * on the JEditorPane. Implemented to forward to the method with the same - * name, but pos == editor == -1. - * - * @param pos - the position - * @param editor - the editor pane + * on the JEditorPane. + * + * @param pos the position + * @param editor the editor pane + */ + protected void activateLink(int pos, JEditorPane editor) + { + activateLink(pos, editor); + } + + private void activateLink(int pos, JEditorPane editor, int x, int y) + { + // TODO: This is here for future extension for mapped links support. + // For the time beeing we implement simple hyperlinks. + Document doc = editor.getDocument(); + if (doc instanceof HTMLDocument) + { + HTMLDocument htmlDoc = (HTMLDocument) doc; + Element el = htmlDoc.getCharacterElement(pos); + AttributeSet atts = el.getAttributes(); + AttributeSet anchorAtts = + (AttributeSet) atts.getAttribute(HTML.Tag.A); + String href = null; + if (anchorAtts != null) + { + href = (String) anchorAtts.getAttribute(HTML.Attribute.HREF); + htmlDoc.updateSpecialClass(el, HTML.Attribute.PSEUDO_CLASS, + "visited"); + } + else + { + // TODO: Implement link maps here. + } + HyperlinkEvent event = null; + if (href != null) + event = createHyperlinkEvent(editor, htmlDoc, href, + anchorAtts, el); + if (event != null) + editor.fireHyperlinkUpdate(event); + } + + } + + /** + * Creates a HyperlinkEvent for the specified href and anchor if + * possible. If for some reason this won't work, return null. + * + * @param editor the editor + * @param doc the document + * @param href the href link + * @param anchor the anchor + * @param el the element + * + * @return the hyperlink event, or <code>null</code> if we couldn't + * create one */ - protected void activateLink(int pos, - JEditorPane editor) + private HyperlinkEvent createHyperlinkEvent(JEditorPane editor, + HTMLDocument doc, + String href, + AttributeSet anchor, + Element el) { - /* - This method creates and fires a HyperlinkEvent if the document is an - instance of HTMLDocument and the href tag of the link is not null. - */ - // FIXME: Not implemented. + URL url; + try + { + URL base = doc.getBase(); + url = new URL(base, href); + + } + catch (MalformedURLException ex) + { + url = null; + } + HyperlinkEvent ev; + if (doc.isFrameDocument()) + { + String target = null; + if (anchor != null) + target = (String) anchor.getAttribute(HTML.Attribute.TARGET); + if (target == null || target.equals("")) + target = doc.getBaseTarget(); + if (target == null || target.equals("")) + target = "_self"; + ev = new HTMLFrameHyperlinkEvent(editor, + HyperlinkEvent.EventType.ACTIVATED, + url, href, el, target); + } + else + { + ev = new HyperlinkEvent(editor, HyperlinkEvent.EventType.ACTIVATED, + url, href, el); + } + return ev; } } @@ -201,7 +341,7 @@ public class HTMLEditorKit * Tag to check for in the document. */ protected HTML.Tag parentTag; - + /** * Initializes all fields. * @@ -305,20 +445,9 @@ public class HTMLEditorKit Element insertElement, String html, HTML.Tag parentTag, HTML.Tag addTag) - throws NotImplementedException { - /* - As its name implies, this protected method is used when HTML is inserted at a - boundary. (A boundary in this case is an offset in doc that exactly matches the - beginning offset of the parentTag.) It performs the extra work required to keep - the tag stack in shape and then calls insertHTML(). The editor and doc argu- - ments are the editor pane and document where the HTML should go. The offset - argument represents the cursor location or selection start in doc. The insert- - Element and parentTag arguments are used to calculate the proper number of - tag pops and pushes before inserting the HTML (via html and addTag, which are - passed directly to insertHTML()). - */ - // FIXME: not implemented + insertAtBoundry(editor, doc, offset, insertElement, + html, parentTag, addTag); } /** @@ -344,8 +473,50 @@ public class HTMLEditorKit String html, HTML.Tag parentTag, HTML.Tag addTag) { - insertAtBoundary(editor, doc, offset, insertElement, - html, parentTag, addTag); + Element parent = insertElement; + Element el; + // Find common parent element. + if (offset > 0 || insertElement == null) + { + el = doc.getDefaultRootElement(); + while (el != null && el.getStartOffset() != offset + && ! el.isLeaf()) + el = el.getElement(el.getElementIndex(offset)); + parent = el != null ? el.getParentElement() : null; + } + if (parent != null) + { + int pops = 0; + int pushes = 0; + if (offset == 0 && insertElement != null) + { + el = parent; + while (el != null && ! el.isLeaf()) + { + el = el.getElement(el.getElementIndex(offset)); + pops++; + } + } + else + { + el = parent; + offset--; + while (el != null && ! el.isLeaf()) + { + el = el.getElement(el.getElementIndex(offset)); + pops++; + } + el = parent; + offset++; + while (el != null && el != insertElement) + { + el = el.getElement(el.getElementIndex(offset)); + pushes++; + } + } + pops = Math.max(0, pops - 1); + insertHTML(editor, doc, offset, html, pops, pushes, addTag); + } } /** @@ -355,16 +526,97 @@ public class HTMLEditorKit */ public void actionPerformed(ActionEvent ae) { - Object source = ae.getSource(); - if (source instanceof JEditorPane) + JEditorPane source = getEditor(ae); + if (source != null) + { + HTMLDocument d = getHTMLDocument(source); + int offset = source.getSelectionStart(); + int length = d.getLength(); + boolean inserted = true; + if (! tryInsert(source, d, offset, parentTag, addTag)) + { + inserted = tryInsert(source, d, offset, alternateParentTag, + alternateAddTag); + } + if (inserted) + adjustSelection(source, d, offset, length); + } + } + + /** + * Tries to insert the html chunk to the specified <code>addTag</code>. + * + * @param pane the editor + * @param doc the document + * @param offset the offset at which to insert + * @param tag the tag at which to insert + * @param addTag the add tag + * + * @return <code>true</code> when the html has been inserted successfully, + * <code>false</code> otherwise + */ + private boolean tryInsert(JEditorPane pane, HTMLDocument doc, int offset, + HTML.Tag tag, HTML.Tag addTag) + { + boolean inserted = false; + Element el = findElementMatchingTag(doc, offset, tag); + if (el != null && el.getStartOffset() == offset) + { + insertAtBoundary(pane, doc, offset, el, html, tag, addTag); + inserted = true; + } + else if (offset > 0) + { + int depth = elementCountToTag(doc, offset - 1, tag); + if (depth != -1) + { + insertHTML(pane, doc, offset, html, depth, 0, addTag); + inserted = true; + } + } + return inserted; + } + + /** + * Adjusts the selection after an insertion has been performed. + * + * @param pane the editor pane + * @param doc the document + * @param offset the insert offset + * @param oldLen the old document length + */ + private void adjustSelection(JEditorPane pane, HTMLDocument doc, + int offset, int oldLen) + { + int newLen = doc.getLength(); + if (newLen != oldLen && offset < newLen) { - JEditorPane pane = ((JEditorPane) source); - Document d = pane.getDocument(); - if (d instanceof HTMLDocument) - insertHTML(pane, (HTMLDocument) d, 0, html, 0, 0, addTag); - // FIXME: is this correct parameters? + if (offset > 0) + { + String text; + try + { + text = doc.getText(offset - 1, 1); + } + catch (BadLocationException ex) + { + text = null; + } + if (text != null && text.length() > 0 + && text.charAt(0) == '\n') + { + pane.select(offset, offset); + } + else + { + pane.select(offset + 1, offset + 1); + } + } + else + { + pane.select(1, 1); + } } - // FIXME: else not implemented } } @@ -540,53 +792,56 @@ public class HTMLEditorKit { HTML.Tag tag = (HTML.Tag) attr; - if (tag.equals(HTML.Tag.IMPLIED) || tag.equals(HTML.Tag.P) - || tag.equals(HTML.Tag.H1) || tag.equals(HTML.Tag.H2) - || tag.equals(HTML.Tag.H3) || tag.equals(HTML.Tag.H4) - || tag.equals(HTML.Tag.H5) || tag.equals(HTML.Tag.H6) - || tag.equals(HTML.Tag.DT)) + if (tag == HTML.Tag.IMPLIED || tag == HTML.Tag.P + || tag == HTML.Tag.H1 || tag == HTML.Tag.H2 + || tag == HTML.Tag.H3 || tag == HTML.Tag.H4 + || tag == HTML.Tag.H5 || tag == HTML.Tag.H6 + || tag == HTML.Tag.DT) view = new ParagraphView(element); - else if (tag.equals(HTML.Tag.LI) || tag.equals(HTML.Tag.DL) - || tag.equals(HTML.Tag.DD) || tag.equals(HTML.Tag.BODY) - || tag.equals(HTML.Tag.HTML) || tag.equals(HTML.Tag.CENTER) - || tag.equals(HTML.Tag.DIV) - || tag.equals(HTML.Tag.BLOCKQUOTE) - || tag.equals(HTML.Tag.PRE)) + else if (tag == HTML.Tag.LI || tag == HTML.Tag.DL + || tag == HTML.Tag.DD || tag == HTML.Tag.BODY + || tag == HTML.Tag.HTML || tag == HTML.Tag.CENTER + || tag == HTML.Tag.DIV + || tag == HTML.Tag.BLOCKQUOTE + || tag == HTML.Tag.PRE + || tag == HTML.Tag.FORM + // Misplaced TD and TH tags get mapped as vertical block. + // Note that correctly placed tags get mapped in TableView. + || tag == HTML.Tag.TD || tag == HTML.Tag.TH) view = new BlockView(element, View.Y_AXIS); - else if (tag.equals(HTML.Tag.IMG)) + else if (tag == HTML.Tag.TR) + // Misplaced TR tags get mapped as horizontal blocks. + // Note that correctly placed tags get mapped in TableView. + view = new BlockView(element, View.X_AXIS); + else if (tag == HTML.Tag.IMG) view = new ImageView(element); - // FIXME: Uncomment when the views have been implemented - else if (tag.equals(HTML.Tag.CONTENT)) + else if (tag == HTML.Tag.CONTENT) view = new InlineView(element); else if (tag == HTML.Tag.HEAD) view = new NullView(element); - else if (tag.equals(HTML.Tag.TABLE)) + else if (tag == HTML.Tag.TABLE) view = new javax.swing.text.html.TableView(element); - else if (tag.equals(HTML.Tag.TD)) - view = new ParagraphView(element); - else if (tag.equals(HTML.Tag.HR)) + else if (tag == HTML.Tag.HR) view = new HRuleView(element); - else if (tag.equals(HTML.Tag.BR)) + else if (tag == HTML.Tag.BR) view = new BRView(element); + else if (tag == HTML.Tag.INPUT || tag == HTML.Tag.SELECT + || tag == HTML.Tag.TEXTAREA) + view = new FormView(element); - /* - else if (tag.equals(HTML.Tag.MENU) || tag.equals(HTML.Tag.DIR) - || tag.equals(HTML.Tag.UL) || tag.equals(HTML.Tag.OL)) + else if (tag == HTML.Tag.MENU || tag == HTML.Tag.DIR + || tag == HTML.Tag.UL || tag == HTML.Tag.OL) view = new ListView(element); - else if (tag.equals(HTML.Tag.INPUT) || tag.equals(HTML.Tag.SELECT) - || tag.equals(HTML.Tag.TEXTAREA)) - view = new FormView(element); - else if (tag.equals(HTML.Tag.OBJECT)) - view = new ObjectView(element); - else if (tag.equals(HTML.Tag.FRAMESET)) + else if (tag == HTML.Tag.FRAMESET) view = new FrameSetView(element); - else if (tag.equals(HTML.Tag.FRAME)) - view = new FrameView(element); */ + else if (tag == HTML.Tag.FRAME) + view = new FrameView(element); + else if (tag == HTML.Tag.OBJECT) + view = new ObjectView(element); } if (view == null) { - System.err.println("missing tag->view mapping for: " + element); view = new NullView(element); } return view; @@ -797,14 +1052,42 @@ public class HTMLEditorKit /** * Actions for HTML */ - private static final Action[] defaultActions = { - // FIXME: Add default actions for html + private static final Action[] defaultActions = + { + new InsertHTMLTextAction("InsertTable", + "<table border=1><tr><td></td></tr></table>", + HTML.Tag.BODY, HTML.Tag.TABLE), + new InsertHTMLTextAction("InsertTableRow", + "<table border=1><tr><td></td></tr></table>", + HTML.Tag.TABLE, HTML.Tag.TR, + HTML.Tag.BODY, HTML.Tag.TABLE), + new InsertHTMLTextAction("InsertTableCell", + "<table border=1><tr><td></td></tr></table>", + HTML.Tag.TR, HTML.Tag.TD, + HTML.Tag.BODY, HTML.Tag.TABLE), + new InsertHTMLTextAction("InsertUnorderedList", + "<ul><li></li></ul>", + HTML.Tag.BODY, HTML.Tag.UL), + new InsertHTMLTextAction("InsertUnorderedListItem", + "<ul><li></li></ul>", + HTML.Tag.UL, HTML.Tag.LI, + HTML.Tag.BODY, HTML.Tag.UL), + new InsertHTMLTextAction("InsertOrderedList", + "<ol><li></li></ol>", + HTML.Tag.BODY, HTML.Tag.OL), + new InsertHTMLTextAction("InsertOrderedListItem", + "<ol><li></li></ol>", + HTML.Tag.OL, HTML.Tag.LI, + HTML.Tag.BODY, HTML.Tag.OL), + new InsertHTMLTextAction("InsertPre", + "<pre></pre>", HTML.Tag.BODY, HTML.Tag.PRE) + // TODO: The reference impl has an InsertHRAction too. }; /** * The current style sheet. */ - StyleSheet styleSheet; + private StyleSheet styleSheet; /** * The ViewFactory for HTMLFactory. @@ -829,12 +1112,7 @@ public class HTMLEditorKit /** * The mouse listener used for links. */ - LinkController mouseListener; - - /** - * Style context for this editor. - */ - StyleContext styleContext; + private LinkController linkController; /** The content type */ String contentType = "text/html"; @@ -844,17 +1122,22 @@ public class HTMLEditorKit /** The editor pane used. */ JEditorPane editorPane; - + + /** + * Whether or not the editor kit handles form submissions. + * + * @see #isAutoFormSubmission() + * @see #setAutoFormSubmission(boolean) + */ + private boolean autoFormSubmission; + /** * Constructs an HTMLEditorKit, creates a StyleContext, and loads the style sheet. */ public HTMLEditorKit() { - super(); - styleContext = new StyleContext(); - styleSheet = new StyleSheet(); - styleSheet.importStyleSheet(getClass().getResource(DEFAULT_CSS)); - // FIXME: Set inputAttributes with default.css + linkController = new LinkController(); + autoFormSubmission = true; } /** @@ -877,8 +1160,15 @@ public class HTMLEditorKit */ public Document createDefaultDocument() { - HTMLDocument document = new HTMLDocument(getStyleSheet()); + // Protect the shared stylesheet. + StyleSheet styleSheet = getStyleSheet(); + StyleSheet ss = new StyleSheet(); + ss.addStyleSheet(styleSheet); + + HTMLDocument document = new HTMLDocument(ss); document.setParser(getParser()); + document.setAsynchronousLoadPriority(4); + document.setTokenThreshold(100); return document; } @@ -892,7 +1182,7 @@ public class HTMLEditorKit { if (parser == null) { - parser = new GnuParserDelegator(HTML_401Swing.getInstance()); + parser = new GnuParserDelegator(HTML_401F.getInstance()); } return parser; } @@ -923,8 +1213,7 @@ public class HTMLEditorKit if (parser == null) throw new IOException("Parser is null."); - ParserCallback pc = ((HTMLDocument) doc).getReader - (offset, popDepth, pushDepth, insertTag); + ParserCallback pc = doc.getReader(offset, popDepth, pushDepth, insertTag); // FIXME: What should ignoreCharSet be set to? @@ -991,8 +1280,15 @@ public class HTMLEditorKit { if (doc instanceof HTMLDocument) { - // FIXME: Not implemented. Use HTMLWriter. - out.write(doc.getText(pos, len)); + HTMLWriter writer = new HTMLWriter(out, (HTMLDocument) doc, pos, len); + writer.write(); + } + else if (doc instanceof StyledDocument) + { + MinimalHTMLWriter writer = new MinimalHTMLWriter(out, + (StyledDocument) doc, + pos, len); + writer.write(); } else super.write(out, doc, pos, len); @@ -1017,7 +1313,9 @@ public class HTMLEditorKit public Object clone() { // FIXME: Need to clone all fields - return (HTMLEditorKit) super.clone(); + HTMLEditorKit copy = (HTMLEditorKit) super.clone(); + copy.linkController = new LinkController(); + return copy; } /** @@ -1044,10 +1342,9 @@ public class HTMLEditorKit public void install(JEditorPane c) { super.install(c); - mouseListener = new LinkController(); - c.addMouseListener(mouseListener); + c.addMouseListener(linkController); + c.addMouseMotionListener(linkController); editorPane = c; - // FIXME: need to set up hyperlinklistener object } /** @@ -1059,8 +1356,8 @@ public class HTMLEditorKit public void deinstall(JEditorPane c) { super.deinstall(c); - c.removeMouseListener(mouseListener); - mouseListener = null; + c.removeMouseListener(linkController); + c.removeMouseMotionListener(linkController); editorPane = null; } @@ -1154,8 +1451,19 @@ public class HTMLEditorKit { if (styleSheet == null) { - styleSheet = new StyleSheet(); - styleSheet.importStyleSheet(getClass().getResource(DEFAULT_CSS)); + try + { + styleSheet = new StyleSheet(); + Class c = HTMLEditorKit.class; + InputStream in = c.getResourceAsStream(DEFAULT_CSS); + InputStreamReader r = new InputStreamReader(in); + styleSheet.loadRules(r, null); + r.close(); + } + catch (IOException ex) + { + // No style available. + } } return styleSheet; } @@ -1173,4 +1481,40 @@ public class HTMLEditorKit { styleSheet = s; } + + /** + * Returns <code>true</code> when forms should be automatically submitted + * by the editor kit. Set this to <code>false</code> when you want to + * intercept form submission. In this case you'd want to listen for + * hyperlink events on the document and handle FormSubmitEvents specially. + * + * The default is <code>true</code>. + * + * @return <code>true</code> when forms should be automatically submitted + * by the editor kit, <code>false</code> otherwise + * + * @since 1.5 + * + * @see #setAutoFormSubmission(boolean) + * @see FormSubmitEvent + */ + public boolean isAutoFormSubmission() + { + return autoFormSubmission; + } + + /** + * Sets whether or not the editor kit should automatically submit forms. + * + * @param auto <code>true</code> when the editor kit should handle form + * submission, <code>false</code> otherwise + * + * @since 1.5 + * + * @see #isAutoFormSubmission() + */ + public void setAutoFormSubmission(boolean auto) + { + autoFormSubmission = auto; + } } diff --git a/libjava/classpath/javax/swing/text/html/HTMLWriter.java b/libjava/classpath/javax/swing/text/html/HTMLWriter.java new file mode 100644 index 00000000000..44119c73286 --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/HTMLWriter.java @@ -0,0 +1,1084 @@ +/* HTMLWriter.java -- + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package javax.swing.text.html; + +import java.io.IOException; +import java.io.Writer; + +import java.util.Enumeration; +import java.util.HashSet; + +import javax.swing.ComboBoxModel; + +import javax.swing.text.AbstractWriter; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.Element; +import javax.swing.text.StyleConstants; + +import javax.swing.text.html.HTML; +import javax.swing.text.html.HTMLDocument; +import javax.swing.text.html.Option; + +/** + * HTMLWriter, + * A Writer for HTMLDocuments. + * + * @author David Fu (fchoong at netbeans.jp) + */ + +public class HTMLWriter + extends AbstractWriter +{ + /** + * We keep a reference of the writer passed by the construct. + */ + private Writer outWriter = null; + + /** + * We keep a reference of the HTMLDocument passed by the construct. + */ + private HTMLDocument htmlDoc = null; + + /** + * Used to keep track of which embeded has been written out. + */ + private HashSet openEmbededTagHashSet = null; + + private String new_line_str = "" + NEWLINE; + + private char[] html_entity_char_arr = {'<', '>', '&', '"'}; + + private String[] html_entity_escape_str_arr = {"<", ">", "&", + """}; + + // variables used to output Html Fragment + private int doc_pos = -1; + private int doc_len = -1; + private int doc_offset_remaining = -1; + private int doc_len_remaining = -1; + private HashSet htmlFragmentParentHashSet = null; + private Element startElem = null; + private Element endElem = null; + private boolean fg_pass_start_elem = false; + private boolean fg_pass_end_elem = false; + + /** + * Constructs a HTMLWriter. + * + * @param writer writer to write output to + * @param doc the HTMLDocument to output + */ + public HTMLWriter(Writer writer, HTMLDocument doc) + { + super(writer, doc); + outWriter = writer; + htmlDoc = doc; + openEmbededTagHashSet = new HashSet(); + } // public HTMLWriter(Writer writer, HTMLDocument doc) + + /** + * Constructs a HTMLWriter which outputs a Html Fragment. + * + * @param writer <code>Writer</code> to write output to + * @param doc the <code>javax.swing.text.html.HTMLDocument</code> + * to output + * @param pos position to start outputing the document + * @param len amount to output the document + */ + public HTMLWriter(Writer writer, HTMLDocument doc, int pos, int len) + { + super(writer, doc, pos, len); + outWriter = writer; + htmlDoc = doc; + openEmbededTagHashSet = new HashSet(); + + doc_pos = pos; + doc_offset_remaining = pos; + doc_len = len; + doc_len_remaining = len; + htmlFragmentParentHashSet = new HashSet(); + } // public HTMLWriter(Writer writer, HTMLDocument doc, int pos, int len) + + /** + * Call this method to start outputing HTML. + * + * @throws IOException on any I/O exceptions + * @throws BadLocationException if a pos is not a valid position in the + * html doc element + */ + public void write() + throws IOException, BadLocationException + { + Element rootElem = htmlDoc.getDefaultRootElement(); + + if (doc_pos == -1 && doc_len == -1) + { + // Normal traversal. + traverse(rootElem); + } // if(doc_pos == -1 && doc_len == -1) + else + { + // Html fragment traversal. + if (doc_pos == -1 || doc_len == -1) + throw new BadLocationException("Bad Location(" + + doc_pos + ", " + doc_len + ")", doc_pos); + + startElem = htmlDoc.getCharacterElement(doc_pos); + + int start_offset = startElem.getStartOffset(); + + // Positions before start_offset will not be traversed, and thus + // will not be counted. + if (start_offset > 0) + doc_offset_remaining = doc_offset_remaining - start_offset; + + Element tempParentElem = startElem; + + while ((tempParentElem = tempParentElem.getParentElement()) != null) + { + if (!htmlFragmentParentHashSet.contains(tempParentElem)) + htmlFragmentParentHashSet.add(tempParentElem); + } // while((tempParentElem = tempParentElem.getParentElement()) + // != null) + + // NOTE: 20061030 - fchoong - the last index should not be included. + endElem = htmlDoc.getCharacterElement(doc_pos + doc_len - 1); + + tempParentElem = endElem; + + while ((tempParentElem = tempParentElem.getParentElement()) != null) + { + if (!htmlFragmentParentHashSet.contains(tempParentElem)) + htmlFragmentParentHashSet.add(tempParentElem); + } // while((tempParentElem = tempParentElem.getParentElement()) + // != null) + + traverseHtmlFragment(rootElem); + + } // else + + // NOTE: close out remaining open embeded tags. + Object[] tag_arr = openEmbededTagHashSet.toArray(); + + for (int i = 0; i < tag_arr.length; i++) + { + writeRaw("</" + tag_arr[i].toString() + ">"); + } // for(int i = 0; i < tag_arr.length; i++) + + } // public void write() throws IOException, BadLocationException + + /** + * Writes all the attributes in the attrSet, except for attrbutes with + * keys of <code>javax.swing.text.html.HTML.Tag</code>, + * <code>javax.swing.text.StyleConstants</code> or + * <code>javax.swing.text.html.HTML.Attribute.ENDTAG</code>. + * + * @param attrSet attrSet to write out + * + * @throws IOException on any I/O exceptions + */ + protected void writeAttributes(AttributeSet attrSet) + throws IOException + { + Enumeration attrNameEnum = attrSet.getAttributeNames(); + + while (attrNameEnum.hasMoreElements()) + { + Object key = attrNameEnum.nextElement(); + Object value = attrSet.getAttribute(key); + + // HTML.Attribute.ENDTAG is an instance, not a class. + if (!((key instanceof HTML.Tag) || (key instanceof StyleConstants) + || (key == HTML.Attribute.ENDTAG))) + { + if (key == HTML.Attribute.SELECTED) + writeRaw(" selected"); + else if (key == HTML.Attribute.CHECKED) + writeRaw(" checked"); + else + writeRaw(" " + key + "=\"" + value + "\""); + } // if(!((key instanceof HTML.Tag) || (key instanceof + // StyleConstants) || (key == HTML.Attribute.ENDTAG))) + } // while(attrNameEnum.hasMoreElements()) + + } // protected void writeAttributes(AttributeSet attrSet) throws IOException + + /** + * Writes out an empty tag. i.e. a tag without any child elements. + * + * @param paramElem the element to output as an empty tag + * + * @throws IOException on any I/O exceptions + * @throws BadLocationException if a pos is not a valid position in the + * html doc element + */ + protected void emptyTag(Element paramElem) + throws IOException, BadLocationException + { + String elem_name = paramElem.getName(); + AttributeSet attrSet = paramElem.getAttributes(); + + writeRaw("<" + elem_name); + writeAttributes(attrSet); + writeRaw(">"); + + if (isBlockTag(attrSet)) + { + writeRaw("</" + elem_name + ">"); + } // if(isBlockTag(attrSet)) + + } // protected void emptyTag(Element paramElem) + // throws IOException, BadLocationException + + /** + * Determines if it is a block tag or not. + * + * @param attrSet the attrSet of the element + * + * @return <code>true</code> if it is a block tag + * <code>false</code> if it is a not block tag + */ + protected boolean isBlockTag(AttributeSet attrSet) + { + return ((HTML.Tag) + attrSet.getAttribute(StyleConstants.NameAttribute)).isBlock(); + } // protected boolean isBlockTag(AttributeSet attrSet) + + /** + * Writes out a start tag. Synthesized elements are skipped. + * + * @param paramElem the element to output as a start tag + * @throws IOException on any I/O exceptions + * @throws BadLocationException if a pos is not a valid position in the + * html doc element + */ + protected void startTag(Element paramElem) + throws IOException, BadLocationException + { + // NOTE: Sysnthesized elements do no call this method at all. + String elem_name = paramElem.getName(); + AttributeSet attrSet = paramElem.getAttributes(); + + indent(); + writeRaw("<" + elem_name); + writeAttributes(attrSet); + writeRaw(">"); + writeLineSeparator(); // Extra formatting to look more like the RI. + incrIndent(); + + } // protected void startTag(Element paramElem) + // throws IOException, BadLocationException + + /** + * Writes out the contents of a textarea. + * + * @param attrSet the attrSet of the element to output as a text area + * @throws IOException on any I/O exceptions + * @throws BadLocationException if a pos is not a valid position in the + * html doc element + */ + protected void textAreaContent(AttributeSet attrSet) + throws IOException, BadLocationException + { + writeLineSeparator(); // Extra formatting to look more like the RI. + indent(); + writeRaw("<textarea"); + writeAttributes(attrSet); + writeRaw(">"); + + Document tempDocument = + (Document) attrSet.getAttribute(StyleConstants.ModelAttribute); + + writeRaw(tempDocument.getText(0, tempDocument.getLength())); + indent(); + writeRaw("</textarea>"); + + } // protected void textAreaContent(AttributeSet attrSet) + // throws IOException, BadLocationException + + /** + * Writes out text, within the appropriate range if it is specified. + * + * @param paramElem the element to output as a text + * @throws IOException on any I/O exceptions + * @throws BadLocationException if a pos is not a valid position in the + * html doc element + */ + protected void text(Element paramElem) + throws IOException, BadLocationException + { + int offset = paramElem.getStartOffset(); + int len = paramElem.getEndOffset() - paramElem.getStartOffset(); + String txt_value = htmlDoc.getText(offset, len); + + writeContent(txt_value); + + } // protected void text(Element paramElem) + // throws IOException, BadLocationException + + /** + * Writes out the contents of a select element. + * + * @param attrSet the attrSet of the element to output as a select box + * + * @throws IOException on any I/O exceptions + */ + protected void selectContent(AttributeSet attrSet) + throws IOException + { + writeLineSeparator(); // Extra formatting to look more like the RI. + indent(); + writeRaw("<select"); + writeAttributes(attrSet); + writeRaw(">"); + incrIndent(); + writeLineSeparator(); // extra formatting to look more like the RI. + + ComboBoxModel comboBoxModel = + (ComboBoxModel) attrSet.getAttribute(StyleConstants.ModelAttribute); + + for (int i = 0; i < comboBoxModel.getSize(); i++) + { + writeOption((Option) comboBoxModel.getElementAt(i)); + } // for(int i = 0; i < comboBoxModel.getSize(); i++) + + decrIndent(); + indent(); + writeRaw("</select>"); + + } // protected void selectContent(AttributeSet attrSet) throws IOException + + /** + * Writes out the contents of an option element. + * + * @param option the option object to output as a select option + * + * @throws IOException on any I/O exceptions + */ + protected void writeOption(Option option) + throws IOException + { + indent(); + writeRaw("<option"); + writeAttributes(option.getAttributes()); + writeRaw(">"); + + writeContent(option.getLabel()); + + writeRaw("</option>"); + writeLineSeparator(); // extra formatting to look more like the RI. + + } // protected void writeOption(Option option) throws IOException + + /** + * Writes out an end tag. + * + * @param paramElem the element to output as an end tag + * + * @throws IOException on any I/O exceptions + */ + protected void endTag(Element paramElem) + throws IOException + { + String elem_name = paramElem.getName(); + + //writeLineSeparator(); // Extra formatting to look more like the RI. + decrIndent(); + indent(); + writeRaw("</" + elem_name + ">"); + writeLineSeparator(); // Extra formatting to look more like the RI. + + } // protected void endTag(Element paramElem) throws IOException + + /** + * Writes out the comment. + * + * @param paramElem the element to output as a comment + */ + protected void comment(Element paramElem) + throws IOException, BadLocationException + { + AttributeSet attrSet = paramElem.getAttributes(); + + String comment_str = (String) attrSet.getAttribute(HTML.Attribute.COMMENT); + + writeRaw("<!--" + comment_str + "-->"); + + } // protected void comment(Element paramElem) + // throws IOException, BadLocationException + + /** + * Determines if element is a synthesized + * <code>javax.swing.text.Element</code> or not. + * + * @param element the element to test + * + * @return <code>true</code> if it is a synthesized element, + * <code>false</code> if it is a not synthesized element + */ + protected boolean synthesizedElement(Element element) + { + AttributeSet attrSet = element.getAttributes(); + Object tagType = attrSet.getAttribute(StyleConstants.NameAttribute); + + if (tagType == HTML.Tag.CONTENT || tagType == HTML.Tag.COMMENT + || tagType == HTML.Tag.IMPLIED) + return true; + else + return false; + } // protected boolean synthesizedElement(Element element) + + /** + * Determines if + * <code>javax.swing.text.StyleConstants.NameAttribute</code> + * matches tag or not. + * + * @param attrSet the <code>javax.swing.text.AttributeSet</code> of + * element to be matched + * @param tag the HTML.Tag to match + * + * @return <code>true</code> if it matches, + * <code>false</code> if it does not match + */ + protected boolean matchNameAttribute(AttributeSet attrSet, HTML.Tag tag) + { + Object tagType = attrSet.getAttribute(StyleConstants.NameAttribute); + + if (tagType == tag) + return true; + else + return false; + } // protected boolean matchNameAttribute(AttributeSet attrSet, + // HTML.Tag tag) + + /** + * Writes out an embedded tag. The tags not already in + * openEmbededTagHashSet will written out. + * + * @param attrSet the <code>javax.swing.text.AttributeSet</code> of + * the element to write out + * + * @throws IOException on any I/O exceptions + */ + protected void writeEmbeddedTags(AttributeSet attrSet) + throws IOException + { + Enumeration attrNameEnum = attrSet.getAttributeNames(); + + while (attrNameEnum.hasMoreElements()) + { + Object key = attrNameEnum.nextElement(); + Object value = attrSet.getAttribute(key); + + if (key instanceof HTML.Tag) + { + if (!openEmbededTagHashSet.contains(key)) + { + writeRaw("<" + key); + writeAttributes((AttributeSet) value); + writeRaw(">"); + openEmbededTagHashSet.add(key); + } // if(!openEmbededTagHashSet.contains(key)) + } // if(key instanceof HTML.Tag) + } // while(attrNameEnum.hasMoreElements()) + + } // protected void writeEmbeddedTags(AttributeSet attrSet) + // throws IOException + + /** + * Closes out an unwanted embedded tag. The tags from the + * openEmbededTagHashSet not found in attrSet will be written out. + * + * @param attrSet the AttributeSet of the element to write out + * + * @throws IOException on any I/O exceptions + */ + protected void closeOutUnwantedEmbeddedTags(AttributeSet attrSet) + throws IOException + { + Object[] tag_arr = openEmbededTagHashSet.toArray(); + + for (int i = 0; i < tag_arr.length; i++) + { + HTML.Tag key = (HTML.Tag) tag_arr[i]; + + if (!attrSet.isDefined(key)) + { + writeRaw("</" + key.toString() + ">"); + openEmbededTagHashSet.remove(key); + } // if(!attrSet.isDefined(key)) + } // for(int i = 0; i < tag_arr.length; i++) + + } // protected void closeOutUnwantedEmbeddedTags(AttributeSet attrSet) + // throws IOException + + /** + * Writes out a line separator. Overwrites the parent to write out a new + * line. + * + * @throws IOException on any I/O exceptions. + */ + protected void writeLineSeparator() + throws IOException + { + writeRaw(new_line_str); + } // protected void writeLineSeparator() throws IOException + + /** + * Write to the writer. Character entites such as <, > + * are escaped appropriately. + * + * @param chars char array to write out + * @param off offset + * @param len length + * + * @throws IOException on any I/O exceptions + */ + protected void output(char[] chars, int off, int len) + throws IOException + { + StringBuffer strBuffer = new StringBuffer(); + + for (int i = 0; i < chars.length; i++) + { + if (isCharHtmlEntity(chars[i])) + strBuffer.append(escapeCharHtmlEntity(chars[i])); + else + strBuffer.append(chars[i]); + } // for(int i = 0; i < chars.length; i++) + + writeRaw(strBuffer.toString()); + + } // protected void output(char[] chars, int off, int len) + // throws IOException + + //------------------------------------------------------------------------- + // private methods + + /** + * The main method used to traverse through the elements. + * + * @param paramElem element to traverse + * + * @throws IOException on any I/O exceptions + */ + private void traverse(Element paramElem) + throws IOException, BadLocationException + { + Element currElem = paramElem; + + AttributeSet attrSet = currElem.getAttributes(); + + closeOutUnwantedEmbeddedTags(attrSet); + + // handle the tag + if (synthesizedElement(paramElem)) + { + if (matchNameAttribute(attrSet, HTML.Tag.CONTENT)) + { + writeEmbeddedTags(attrSet); + text(currElem); + } // if(matchNameAttribute(attrSet, HTML.Tag.CONTENT)) + else if (matchNameAttribute(attrSet, HTML.Tag.COMMENT)) + { + comment(currElem); + } // else if(matchNameAttribute(attrSet, HTML.Tag.COMMENT)) + else if (matchNameAttribute(attrSet, HTML.Tag.IMPLIED)) + { + int child_elem_count = currElem.getElementCount(); + + if (child_elem_count > 0) + { + for (int i = 0; i < child_elem_count; i++) + { + Element childElem = paramElem.getElement(i); + + traverse(childElem); + + } // for(int i = 0; i < child_elem_count; i++) + } // if(child_elem_count > 0) + } // else if(matchNameAttribute(attrSet, HTML.Tag.IMPLIED)) + } // if(synthesizedElement(paramElem)) + else + { + // NOTE: 20061030 - fchoong - title is treated specially here. + // based on RI behavior. + if (matchNameAttribute(attrSet, HTML.Tag.TITLE)) + { + boolean fg_is_end_tag = false; + Enumeration attrNameEnum = attrSet.getAttributeNames(); + + while (attrNameEnum.hasMoreElements()) + { + Object key = attrNameEnum.nextElement(); + Object value = attrSet.getAttribute(key); + + if (key == HTML.Attribute.ENDTAG && value.equals("true")) + fg_is_end_tag = true; + } // while(attrNameEnum.hasMoreElements()) + + if (fg_is_end_tag) + writeRaw("</title>"); + else + { + indent(); + writeRaw("<title>"); + + String title_str = + (String) htmlDoc.getProperty(HTMLDocument.TitleProperty); + + if (title_str != null) + writeContent(title_str); + + } // else + } // if(matchNameAttribute(attrSet, HTML.Tag.TITLE)) + else if (matchNameAttribute(attrSet, HTML.Tag.PRE)) + { + // We pursue more stringent formating here. + attrSet = paramElem.getAttributes(); + + indent(); + writeRaw("<pre"); + writeAttributes(attrSet); + writeRaw(">"); + + int child_elem_count = currElem.getElementCount(); + + for (int i = 0; i < child_elem_count; i++) + { + Element childElem = paramElem.getElement(i); + + traverse(childElem); + + } // for(int i = 0; i < child_elem_count; i++) + + writeRaw("</pre>"); + + } // else if(matchNameAttribute(attrSet, HTML.Tag.PRE)) + else if (matchNameAttribute(attrSet, HTML.Tag.SELECT)) + { + selectContent(attrSet); + } // else if(matchNameAttribute(attrSet, HTML.Tag.SELECT)) + else if (matchNameAttribute(attrSet, HTML.Tag.TEXTAREA)) + { + textAreaContent(attrSet); + } // else if(matchNameAttribute(attrSet, HTML.Tag.TEXTAREA)) + else + { + int child_elem_count = currElem.getElementCount(); + + if (child_elem_count > 0) + { + startTag(currElem); + + for (int i = 0; i < child_elem_count; i++) + { + Element childElem = paramElem.getElement(i); + + traverse(childElem); + + } // for(int i = 0; i < child_elem_count; i++) + + endTag(currElem); + + } // if(child_elem_count > 0) + else + { + emptyTag(currElem); + } // else + } // else + } // else + + } // private void traverse(Element paramElem) + // throws IOException, BadLocationException + + /** + * The method used to traverse through a html fragment. + * + * @param paramElem element to traverse + * + * @throws IOException on any I/O exceptions + */ + private void traverseHtmlFragment(Element paramElem) + throws IOException, BadLocationException + { + // NOTE: This method is similar to traverse(Element paramElem) + Element currElem = paramElem; + + boolean fg_is_fragment_parent_elem = false; + boolean fg_is_start_and_end_elem = false; + + if (htmlFragmentParentHashSet.contains(paramElem)) + fg_is_fragment_parent_elem = true; + + if (paramElem == startElem) + fg_pass_start_elem = true; + + if (paramElem == startElem && paramElem == endElem) + fg_is_start_and_end_elem = true; + + AttributeSet attrSet = currElem.getAttributes(); + + closeOutUnwantedEmbeddedTags(attrSet); + + if (fg_is_fragment_parent_elem || (fg_pass_start_elem + && fg_pass_end_elem == false) || fg_is_start_and_end_elem) + { + // handle the tag + if (synthesizedElement(paramElem)) + { + if (matchNameAttribute(attrSet, HTML.Tag.CONTENT)) + { + writeEmbeddedTags(attrSet); + + int content_offset = paramElem.getStartOffset(); + int content_length = currElem.getEndOffset() - content_offset; + + if (doc_offset_remaining > 0) + { + if (content_length > doc_offset_remaining) + { + int split_len = content_length; + + split_len = split_len - doc_offset_remaining; + + if (split_len > doc_len_remaining) + split_len = doc_len_remaining; + + // we need to split it. + String txt_value = htmlDoc.getText(content_offset + + doc_offset_remaining, split_len); + + writeContent(txt_value); + + doc_offset_remaining = 0; // the offset is used up. + doc_len_remaining = doc_len_remaining - split_len; + } // if(content_length > doc_offset_remaining) + else + { + // doc_offset_remaining is greater than the entire + // length of content + doc_offset_remaining = doc_offset_remaining + - content_length; + } // else + } // if(doc_offset_remaining > 0) + else if (content_length <= doc_len_remaining) + { + // we can fit the entire content. + text(currElem); + doc_len_remaining = doc_len_remaining - content_length; + } // else if(content_length <= doc_len_remaining) + else + { + // we need to split it. + String txt_value = htmlDoc.getText(content_offset, + doc_len_remaining); + + writeContent(txt_value); + + doc_len_remaining = 0; + } // else + + } // if(matchNameAttribute(attrSet, HTML.Tag.CONTENT)) + else if (matchNameAttribute(attrSet, HTML.Tag.COMMENT)) + { + comment(currElem); + } // else if(matchNameAttribute(attrSet, HTML.Tag.COMMENT)) + else if (matchNameAttribute(attrSet, HTML.Tag.IMPLIED)) + { + int child_elem_count = currElem.getElementCount(); + + if (child_elem_count > 0) + { + for (int i = 0; i < child_elem_count; i++) + { + Element childElem = paramElem.getElement(i); + + traverseHtmlFragment(childElem); + + } // for(int i = 0; i < child_elem_count; i++) + } // if(child_elem_count > 0) + } // else if(matchNameAttribute(attrSet, HTML.Tag.IMPLIED)) + } // if(synthesizedElement(paramElem)) + else + { + // NOTE: 20061030 - fchoong - the isLeaf() condition seems to + // generate the closest behavior to the RI. + if (paramElem.isLeaf()) + { + if (doc_offset_remaining > 0) + { + doc_offset_remaining--; + } // if(doc_offset_remaining > 0) + else if (doc_len_remaining > 0) + { + doc_len_remaining--; + } // else if(doc_len_remaining > 0) + } // if(paramElem.isLeaf()) + + // NOTE: 20061030 - fchoong - title is treated specially here. + // based on RI behavior. + if (matchNameAttribute(attrSet, HTML.Tag.TITLE)) + { + boolean fg_is_end_tag = false; + Enumeration attrNameEnum = attrSet.getAttributeNames(); + + while (attrNameEnum.hasMoreElements()) + { + Object key = attrNameEnum.nextElement(); + Object value = attrSet.getAttribute(key); + + if (key == HTML.Attribute.ENDTAG && value.equals("true")) + fg_is_end_tag = true; + } // while(attrNameEnum.hasMoreElements()) + + if (fg_is_end_tag) + writeRaw("</title>"); + else + { + indent(); + writeRaw("<title>"); + + String title_str = + (String) htmlDoc.getProperty(HTMLDocument.TitleProperty); + + if (title_str != null) + writeContent(title_str); + + } // else + } // if(matchNameAttribute(attrSet, HTML.Tag.TITLE)) + else if (matchNameAttribute(attrSet, HTML.Tag.PRE)) + { + // We pursue more stringent formating here. + attrSet = paramElem.getAttributes(); + + indent(); + writeRaw("<pre"); + writeAttributes(attrSet); + writeRaw(">"); + + int child_elem_count = currElem.getElementCount(); + + for (int i = 0; i < child_elem_count; i++) + { + Element childElem = paramElem.getElement(i); + + traverseHtmlFragment(childElem); + + } // for(int i = 0; i < child_elem_count; i++) + + writeRaw("</pre>"); + + } // else if(matchNameAttribute(attrSet, HTML.Tag.PRE)) + else if (matchNameAttribute(attrSet, HTML.Tag.SELECT)) + { + selectContent(attrSet); + } // else if(matchNameAttribute(attrSet, HTML.Tag.SELECT)) + else if (matchNameAttribute(attrSet, HTML.Tag.TEXTAREA)) + { + textAreaContent(attrSet); + } // else if(matchNameAttribute(attrSet, HTML.Tag.TEXTAREA)) + else + { + int child_elem_count = currElem.getElementCount(); + + if (child_elem_count > 0) + { + startTag(currElem); + + for (int i = 0; i < child_elem_count; i++) + { + Element childElem = paramElem.getElement(i); + + traverseHtmlFragment(childElem); + + } // for(int i = 0; i < child_elem_count; i++) + + endTag(currElem); + + } // if(child_elem_count > 0) + else + { + emptyTag(currElem); + } // else + } // else + } // else + + } // if(fg_is_fragment_parent_elem || (fg_pass_start_elem + // && fg_pass_end_elem == false) || fg_is_start_and_end_elem) + + if (paramElem == endElem) + fg_pass_end_elem = true; + + } // private void traverseHtmlFragment(Element paramElem) + // throws IOException, BadLocationException + + /** + * Write to the writer without any modifications. + * + * @param param_str the str to write out + * + * @throws IOException on any I/O exceptions + */ + private void writeRaw(String param_str) + throws IOException + { + super.output(param_str.toCharArray(), 0, param_str.length()); + } // private void writeRaw(char[] chars, int off, int len) + // throws IOException + + /** + * Write to the writer, escaping HTML character entitie where neccessary. + * + * @param param_str the str to write out + * + * @throws IOException on any I/O exceptions + */ + private void writeContent(String param_str) + throws IOException + { + char[] str_char_arr = param_str.toCharArray(); + + if (hasHtmlEntity(param_str)) + output(str_char_arr, 0, str_char_arr.length); + else + super.output(str_char_arr, 0, str_char_arr.length); + + } // private void writeContent(String param_str) throws IOException + + /** + * Use this for debugging. Writes out all attributes regardless of type. + * + * @param attrSet the <code>javax.swing.text.AttributeSet</code> to + * write out + * + * @throws IOException on any I/O exceptions + */ + private void writeAllAttributes(AttributeSet attrSet) + throws IOException + { + Enumeration attrNameEnum = attrSet.getAttributeNames(); + + while (attrNameEnum.hasMoreElements()) + { + Object key = attrNameEnum.nextElement(); + Object value = attrSet.getAttribute(key); + + writeRaw(" " + key + "=\"" + value + "\""); + writeRaw(" " + key.getClass().toString() + "=\"" + + value.getClass().toString() + "\""); + } // while(attrNameEnum.hasMoreElements()) + + } // private void writeAllAttributes(AttributeSet attrSet) + // throws IOException + + /** + * Tests if the str contains any html entities. + * + * @param param_str the str to test + * + * @return <code>true</code> if it has a html entity + * <code>false</code> if it does not have a html entity + */ + private boolean hasHtmlEntity(String param_str) + { + boolean ret_bool = false; + + for (int i = 0; i < html_entity_char_arr.length; i++) + { + if (param_str.indexOf(html_entity_char_arr[i]) != -1) + { + ret_bool = true; + break; + } // if(param_str.indexOf(html_entity_char_arr[i]) != -1) + } // for(int i = 0; i < html_entity_char_arr.length; i++) + + return ret_bool; + } // private boolean hasHtmlEntity(String param_str) + + /** + * Tests if the char is a html entities. + * + * @param param_char the char to test + * + * @return <code>true</code> if it is a html entity + * <code>false</code> if it is not a html entity. + */ + private boolean isCharHtmlEntity(char param_char) + { + boolean ret_bool = false; + + for (int i = 0; i < html_entity_char_arr.length; i++) + { + if (param_char == html_entity_char_arr[i]) + { + ret_bool = true; + break; + } // if(param_char == html_entity_char_arr[i]) + } // for(int i = 0; i < html_entity_char_arr.length; i++) + + return ret_bool; + } // private boolean hasHtmlEntity(String param_str) + + /** + * Escape html entities. + * + * @param param_char the char to escape + * + * @return escaped html entity. Original char is returned as a str if is + * is not a html entity + */ + private String escapeCharHtmlEntity(char param_char) + { + String ret_str = "" + param_char; + + for (int i = 0; i < html_entity_char_arr.length; i++) + { + if (param_char == html_entity_char_arr[i]) + { + ret_str = html_entity_escape_str_arr[i]; + break; + } // if(param_char == html_entity_char_arr[i]) + } // for(int i = 0; i < html_entity_char_arr.length; i++) + + return ret_str; + } // private String escapeCharHtmlEntity(char param_char) + +} // public class HTMLWriter extends AbstractWriter
\ No newline at end of file diff --git a/libjava/classpath/javax/swing/text/html/ImageView.java b/libjava/classpath/javax/swing/text/html/ImageView.java index 84b021070a9..bf906e4500e 100644 --- a/libjava/classpath/javax/swing/text/html/ImageView.java +++ b/libjava/classpath/javax/swing/text/html/ImageView.java @@ -1,18 +1,21 @@ package javax.swing.text.html; -import gnu.javax.swing.text.html.CombinedAttributes; import gnu.javax.swing.text.html.ImageViewIconFactory; +import gnu.javax.swing.text.html.css.Length; import java.awt.Graphics; import java.awt.Image; import java.awt.MediaTracker; import java.awt.Rectangle; import java.awt.Shape; +import java.awt.Toolkit; +import java.awt.image.ImageObserver; import java.net.MalformedURLException; import java.net.URL; import javax.swing.Icon; -import javax.swing.ImageIcon; +import javax.swing.SwingUtilities; +import javax.swing.text.AbstractDocument; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.Document; @@ -29,15 +32,38 @@ import javax.swing.text.html.HTML.Attribute; public class ImageView extends View { /** + * Tracks image loading state and performs the necessary layout updates. + */ + class Observer + implements ImageObserver + { + + public boolean imageUpdate(Image image, int flags, int x, int y, int width, int height) + { + boolean widthChanged = false; + if ((flags & ImageObserver.WIDTH) != 0 && spans[X_AXIS] == null) + widthChanged = true; + boolean heightChanged = false; + if ((flags & ImageObserver.HEIGHT) != 0 && spans[Y_AXIS] == null) + heightChanged = true; + if (widthChanged || heightChanged) + safePreferenceChanged(ImageView.this, widthChanged, heightChanged); + boolean ret = (flags & ALLBITS) != 0; + return ret; + } + + } + + /** * True if the image loads synchronuosly (on demand). By default, the image * loads asynchronuosly. */ boolean loadOnDemand; - + /** * The image icon, wrapping the image, */ - ImageIcon imageIcon; + Image image; /** * The image state. @@ -45,6 +71,58 @@ public class ImageView extends View byte imageState = MediaTracker.LOADING; /** + * True when the image needs re-loading, false otherwise. + */ + private boolean reloadImage; + + /** + * True when the image properties need re-loading, false otherwise. + */ + private boolean reloadProperties; + + /** + * True when the width is set as CSS/HTML attribute. + */ + private boolean haveWidth; + + /** + * True when the height is set as CSS/HTML attribute. + */ + private boolean haveHeight; + + /** + * True when the image is currently loading. + */ + private boolean loading; + + /** + * The current width of the image. + */ + private int width; + + /** + * The current height of the image. + */ + private int height; + + /** + * Our ImageObserver for tracking the loading state. + */ + private ImageObserver observer; + + /** + * The CSS width and height. + * + * Package private to avoid synthetic accessor methods. + */ + Length[] spans; + + /** + * The cached attributes. + */ + private AttributeSet attributes; + + /** * Creates the image view that represents the given element. * * @param element the element, represented by this image view. @@ -52,25 +130,36 @@ public class ImageView extends View public ImageView(Element element) { super(element); + spans = new Length[2]; + observer = new Observer(); + reloadProperties = true; + reloadImage = true; + loadOnDemand = false; } /** * Load or reload the image. This method initiates the image reloading. After * the image is ready, the repaint event will be scheduled. The current image, * if it already exists, will be discarded. - * - * @param itsTime - * also load if the "on demand" property is set */ - void reloadImage(boolean itsTime) + private void reloadImage() { - URL url = getImageURL(); - if (url == null) - imageState = (byte) MediaTracker.ERRORED; - else if (!(loadOnDemand && !itsTime)) - imageIcon = new ImageIcon(url); - else - imageState = (byte) MediaTracker.LOADING; + loading = true; + reloadImage = false; + haveWidth = false; + haveHeight = false; + image = null; + width = 0; + height = 0; + try + { + loadImage(); + updateSize(); + } + finally + { + loading = false; + } } /** @@ -146,12 +235,9 @@ public class ImageView extends View */ public AttributeSet getAttributes() { - StyleSheet styles = getStyleSheet(); - if (styles == null) - return super.getAttributes(); - else - return CombinedAttributes.combine(super.getAttributes(), - styles.getViewAttributes(this)); + if (attributes == null) + attributes = getStyleSheet().getViewAttributes(this); + return attributes; } /** @@ -159,10 +245,8 @@ public class ImageView extends View */ public Image getImage() { - if (imageIcon == null) - return null; - else - return imageIcon.getImage(); + updateState(); + return image; } /** @@ -175,19 +259,22 @@ public class ImageView extends View */ public URL getImageURL() { - Object url = getAttributes().getAttribute(Attribute.SRC); - if (url == null) - return null; - - try + Element el = getElement(); + String src = (String) el.getAttributes().getAttribute(Attribute.SRC); + URL url = null; + if (src != null) { - return new URL(url.toString()); - } - catch (MalformedURLException e) - { - // The URL is malformed - no image. - return null; + URL base = ((HTMLDocument) getDocument()).getBase(); + try + { + url = new URL(base, src); + } + catch (MalformedURLException ex) + { + // Return null. + } } + return url; } /** @@ -242,9 +329,8 @@ public class ImageView extends View if (axis == View.X_AXIS) { - Object w = attrs.getAttribute(Attribute.WIDTH); - if (w != null) - return Integer.parseInt(w.toString()); + if (spans[axis] != null) + return spans[axis].getValue(); else if (image != null) return image.getWidth(getContainer()); else @@ -252,9 +338,8 @@ public class ImageView extends View } else if (axis == View.Y_AXIS) { - Object w = attrs.getAttribute(Attribute.HEIGHT); - if (w != null) - return Integer.parseInt(w.toString()); + if (spans[axis] != null) + return spans[axis].getValue(); else if (image != null) return image.getHeight(getContainer()); else @@ -271,11 +356,8 @@ public class ImageView extends View */ protected StyleSheet getStyleSheet() { - Document d = getElement().getDocument(); - if (d instanceof HTMLDocument) - return ((HTMLDocument) d).getStyleSheet(); - else - return null; + HTMLDocument doc = (HTMLDocument) getDocument(); + return doc.getStyleSheet(); } /** @@ -288,7 +370,7 @@ public class ImageView extends View { return getAltText(); } - + /** * Paints the image or one of the two image state icons. The image is resized * to the shape bounds. If there is no image available, the alternative text @@ -302,83 +384,22 @@ public class ImageView extends View */ public void paint(Graphics g, Shape bounds) { - Rectangle r = bounds.getBounds(); - - if (imageIcon == null) - - { - // Loading image on demand, rendering the loading icon so far. - reloadImage(true); - - // The reloadImage sets the imageIcon, unless the URL is broken - // or malformed. - if (imageIcon != null) - { - if (imageIcon.getImageLoadStatus() != MediaTracker.COMPLETE) - { - // Render "not ready" icon, unless the image is ready - // immediately. - renderIcon(g, r, getLoadingImageIcon()); - // Add the listener to repaint when the icon will be ready. - imageIcon.setImageObserver(getContainer()); - return; - } - } - else - { - renderIcon(g, r, getNoImageIcon()); - return; - } - } - - imageState = (byte) imageIcon.getImageLoadStatus(); - - switch (imageState) - { - case MediaTracker.ABORTED: - case MediaTracker.ERRORED: - renderIcon(g, r, getNoImageIcon()); - break; - case MediaTracker.LOADING: - // If the image is not loaded completely, we still render it, as the - // partial image may be available. - case MediaTracker.COMPLETE: + updateState(); + Rectangle r = bounds instanceof Rectangle ? (Rectangle) bounds + : bounds.getBounds(); + Image image = getImage(); + if (image != null) { - // Paint the scaled image. - Image scaled = imageIcon.getImage().getScaledInstance( - r.width, - r.height, - Image.SCALE_DEFAULT); - ImageIcon painter = new ImageIcon(scaled); - painter.paintIcon(getContainer(), g, r.x, r.y); - } - break; + g.drawImage(image, r.x, r.y, r.width, r.height, observer); } - } - - /** - * Render "no image" icon and the alternative "no image" text. The text is - * rendered right from the icon and is aligned to the icon bottom. - */ - private void renderIcon(Graphics g, Rectangle bounds, Icon icon) - { - Shape current = g.getClip(); - try + else { - g.setClip(bounds); + Icon icon = getNoImageIcon(); if (icon != null) - { - icon.paintIcon(getContainer(), g, bounds.x, bounds.y); - g.drawString(getAltText(), bounds.x + icon.getIconWidth(), - bounds.y + icon.getIconHeight()); - } - } - finally - { - g.setClip(current); + icon.paintIcon(getContainer(), g, r.x, r.y); } } - + /** * Set if the image should be loaded only when needed (synchronuosly). By * default, the image loads asynchronuosly. If the image is not yet ready, the @@ -395,9 +416,20 @@ public class ImageView extends View */ protected void setPropertiesFromAttributes() { - // In the current implementation, nothing is cached yet, unless the image - // itself. - imageIcon = null; + AttributeSet atts = getAttributes(); + StyleSheet ss = getStyleSheet(); + float emBase = ss.getEMBase(atts); + float exBase = ss.getEXBase(atts); + spans[X_AXIS] = (Length) atts.getAttribute(CSS.Attribute.WIDTH); + if (spans[X_AXIS] != null) + { + spans[X_AXIS].setFontBases(emBase, exBase); + } + spans[Y_AXIS] = (Length) atts.getAttribute(CSS.Attribute.HEIGHT); + if (spans[Y_AXIS] != null) + { + spans[Y_AXIS].setFontBases(emBase, exBase); + } } /** @@ -433,9 +465,130 @@ public class ImageView extends View */ public void setSize(float width, float height) { - if (imageIcon == null) - reloadImage(false); + updateState(); + // TODO: Implement this when we have an alt view for the alt=... attribute. } - + /** + * This makes sure that the image and properties have been loaded. + */ + private void updateState() + { + if (reloadImage) + reloadImage(); + if (reloadProperties) + setPropertiesFromAttributes(); + } + + /** + * Actually loads the image. + */ + private void loadImage() + { + URL src = getImageURL(); + Image newImage = null; + if (src != null) + { + // Call getImage(URL) to allow the toolkit caching of that image URL. + Toolkit tk = Toolkit.getDefaultToolkit(); + newImage = tk.getImage(src); + tk.prepareImage(newImage, -1, -1, observer); + if (newImage != null && getLoadsSynchronously()) + { + // Load image synchronously. + MediaTracker tracker = new MediaTracker(getContainer()); + tracker.addImage(newImage, 0); + try + { + tracker.waitForID(0); + } + catch (InterruptedException ex) + { + Thread.interrupted(); + } + + } + } + image = newImage; + } + + /** + * Updates the size parameters of the image. + */ + private void updateSize() + { + int newW = 0; + int newH = 0; + Image newIm = getImage(); + if (newIm != null) + { + AttributeSet atts = getAttributes(); + // Fetch width. + Length l = spans[X_AXIS]; + if (l != null) + { + newW = (int) l.getValue(); + haveWidth = true; + } + else + { + newW = newIm.getWidth(observer); + } + // Fetch height. + l = spans[Y_AXIS]; + if (l != null) + { + newH = (int) l.getValue(); + haveHeight = true; + } + else + { + newW = newIm.getWidth(observer); + } + // Go and trigger loading. + Toolkit tk = Toolkit.getDefaultToolkit(); + if (haveWidth || haveHeight) + tk.prepareImage(newIm, width, height, observer); + else + tk.prepareImage(newIm, -1, -1, observer); + } + } + + /** + * Calls preferenceChanged from the event dispatch thread and within + * a read lock to protect us from threading issues. + * + * @param v the view + * @param width true when the width changed + * @param height true when the height changed + */ + void safePreferenceChanged(final View v, final boolean width, + final boolean height) + { + if (SwingUtilities.isEventDispatchThread()) + { + Document doc = getDocument(); + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readLock(); + try + { + preferenceChanged(v, width, height); + } + finally + { + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readUnlock(); + } + } + else + { + SwingUtilities.invokeLater(new Runnable() + { + public void run() + { + safePreferenceChanged(v, width, height); + } + }); + } + } } diff --git a/libjava/classpath/javax/swing/text/html/InlineView.java b/libjava/classpath/javax/swing/text/html/InlineView.java index 77ec86e8263..58edc738526 100644 --- a/libjava/classpath/javax/swing/text/html/InlineView.java +++ b/libjava/classpath/javax/swing/text/html/InlineView.java @@ -38,13 +38,17 @@ exception statement from your version. */ package javax.swing.text.html; +import java.awt.FontMetrics; import java.awt.Shape; +import java.text.BreakIterator; import javax.swing.event.DocumentEvent; import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.Element; import javax.swing.text.LabelView; +import javax.swing.text.Segment; import javax.swing.text.View; import javax.swing.text.ViewFactory; @@ -60,6 +64,23 @@ public class InlineView { /** + * The attributes used by this view. + */ + private AttributeSet attributes; + + /** + * The span of the longest word in this view. + * + * @see #getLongestWord() + */ + private float longestWord; + + /** + * Indicates if we may wrap or not. + */ + private boolean nowrap; + + /** * Creates a new <code>InlineView</code> that renders the specified element. * * @param element the element for this view @@ -115,6 +136,9 @@ public class InlineView public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) { super.changedUpdate(e, a, f); + StyleSheet ss = getStyleSheet(); + attributes = ss.getViewAttributes(this); + preferenceChanged(null, true, true); setPropertiesFromAttributes(); } @@ -126,15 +150,23 @@ public class InlineView */ public AttributeSet getAttributes() { - // FIXME: Implement this. - return super.getAttributes(); + if (attributes == null) + { + StyleSheet ss = getStyleSheet(); + attributes = ss.getViewAttributes(this); + } + return attributes; } public int getBreakWeight(int axis, float pos, float len) { - // FIXME: Implement this. - return super.getBreakWeight(axis, pos, len); + int weight; + if (nowrap) + weight = BadBreakWeight; + else + weight = super.getBreakWeight(axis, pos, len); + return weight; } public View breakView(int axis, int offset, float pos, float len) @@ -143,10 +175,48 @@ public class InlineView return super.breakView(axis, offset, pos, len); } + /** + * Loads the character style properties from the stylesheet. + */ protected void setPropertiesFromAttributes() { - // FIXME: Implement this. super.setPropertiesFromAttributes(); + AttributeSet atts = getAttributes(); + Object o = atts.getAttribute(CSS.Attribute.TEXT_DECORATION); + + // Check for underline. + boolean b = false; + if (o != null && o.toString().contains("underline")) + b = true; + setUnderline(b); + + // Check for line-through. + b = false; + if (o != null && o.toString().contains("line-through")) + b = true; + setStrikeThrough(b); + + // Check for vertical alignment (subscript/superscript). + o = atts.getAttribute(CSS.Attribute.VERTICAL_ALIGN); + + // Subscript. + b = false; + if (o != null && o.toString().contains("sub")) + b = true; + setSubscript(b); + + // Superscript. + b = false; + if (o != null && o.toString().contains("sup")) + b = true; + setSuperscript(b); + + // Fetch nowrap setting. + o = atts.getAttribute(CSS.Attribute.WHITE_SPACE); + if (o != null && o.equals("nowrap")) + nowrap = true; + else + nowrap = false; } /** @@ -163,4 +233,75 @@ public class InlineView styleSheet = ((HTMLDocument) doc).getStyleSheet(); return styleSheet; } + + /** + * Returns the minimum span for the specified axis. This returns the + * width of the longest word for the X axis and the super behaviour for + * the Y axis. This is a slight deviation from the reference implementation. + * IMO this should improve rendering behaviour so that an InlineView never + * gets smaller than the longest word in it. + */ + public float getMinimumSpan(int axis) + { + float min = super.getMinimumSpan(axis); + if (axis == X_AXIS) + min = Math.max(getLongestWord(), min); + return min; + } + + /** + * Returns the span of the longest word in this view. + * + * @return the span of the longest word in this view + */ + private float getLongestWord() + { + if (longestWord == -1) + longestWord = calculateLongestWord(); + return longestWord; + } + + /** + * Calculates the span of the longest word in this view. + * + * @return the span of the longest word in this view + */ + private float calculateLongestWord() + { + float span = 0; + try + { + Document doc = getDocument(); + int p0 = getStartOffset(); + int p1 = getEndOffset(); + Segment s = new Segment(); + doc.getText(p0, p1 - p0, s); + BreakIterator iter = BreakIterator.getWordInstance(); + iter.setText(s); + int wordStart = p0; + int wordEnd = p0; + int start = iter.first(); + for (int end = iter.next(); end != BreakIterator.DONE; + start = end, end = iter.next()) + { + if ((end - start) > (wordEnd - wordStart)) + { + wordStart = start; + wordEnd = end; + } + } + if (wordEnd - wordStart > 0) + { + FontMetrics fm = getFontMetrics(); + int offset = s.offset + wordStart - s.getBeginIndex(); + span = fm.charsWidth(s.array, offset, wordEnd - wordStart); + } + } + catch (BadLocationException ex) + { + // Return 0. + } + return span; + } + } diff --git a/libjava/classpath/javax/swing/text/html/ListView.java b/libjava/classpath/javax/swing/text/html/ListView.java index c07d3598c92..3e809bbd209 100644 --- a/libjava/classpath/javax/swing/text/html/ListView.java +++ b/libjava/classpath/javax/swing/text/html/ListView.java @@ -94,9 +94,6 @@ public class ListView public void paint(Graphics g, Shape allocation) { super.paint(g, allocation); - // FIXME: Why is this overridden? I think that painting would be done - // by the superclass and the stylesheet... Maybe find out when this - // stuff is implemented properly. } /** diff --git a/libjava/classpath/javax/swing/text/html/MultiAttributeSet.java b/libjava/classpath/javax/swing/text/html/MultiAttributeSet.java new file mode 100644 index 00000000000..0f1145084e1 --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/MultiAttributeSet.java @@ -0,0 +1,213 @@ +/* MultiAttributeSet.java -- Multiplexes between a set of AttributeSets + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import java.util.Enumeration; +import java.util.NoSuchElementException; + +import javax.swing.text.AttributeSet; +import javax.swing.text.SimpleAttributeSet; + +/** + * An AttributeSet impl that multiplexes between a set of other AttributeSets. + * + * @author Roman Kennke (kennke@aicas.com) + */ +class MultiAttributeSet + implements AttributeSet +{ + + /** + * The Enumeration for the multiplexed names. + */ + private class MultiNameEnumeration + implements Enumeration + { + /** + * The index of the current AttributeSet. + */ + private int index; + + /** + * The names Enumeration of the current AttributeSet. + */ + private Enumeration current; + + /** + * Creates a new instance. + */ + MultiNameEnumeration() + { + index = 0; + current = multi[0].getAttributeNames(); + } + + public boolean hasMoreElements() + { + return current.hasMoreElements() || index < multi.length - 1; + } + + public Object nextElement() + { + if (! current.hasMoreElements()) + { + if (index < multi.length - 1) + { + index++; + current = multi[index].getAttributeNames(); + } + else + throw new NoSuchElementException(); + } + return current.nextElement(); + } + + } + + /** + * The AttributeSets to multiplex. + */ + AttributeSet[] multi; + + /** + * Provided for subclasses that need to initialize via {@link #init}. + */ + MultiAttributeSet() + { + // Nothing to do here. + } + + /** + * Creates a new instance. + * + * @param m the AttributeSets to multiplex + */ + MultiAttributeSet(AttributeSet[] m) + { + init(m); + } + + /** + * Provided for subclasses to initialize the attribute set. + * + * @param m the attributes to multiplex + */ + void init(AttributeSet[] m) + { + multi = m; + } + + public boolean containsAttribute(Object name, Object value) + { + boolean ret = false; + for (int i = 0; i < multi.length && ret == false; i++) + { + if (multi[i].containsAttribute(name, value)) + ret = true; + } + return ret; + } + + public boolean containsAttributes(AttributeSet attributes) + { + boolean ret = true; + Enumeration e = attributes.getAttributeNames(); + while (ret && e.hasMoreElements()) + { + Object key = e.nextElement(); + ret = attributes.getAttribute(key).equals(getAttribute(key)); + } + return ret; + } + + public AttributeSet copyAttributes() + { + SimpleAttributeSet copy = new SimpleAttributeSet(); + for (int i = 0; i < multi.length; i++) + { + copy.addAttributes(multi[i]); + } + return copy; + } + + public Object getAttribute(Object key) + { + Object ret = null; + for (int i = 0; i < multi.length && ret == null; i++) + { + ret = multi[i].getAttribute(key); + } + return ret; + } + + public int getAttributeCount() + { + int n = 0; + for (int i = 0; i < multi.length; i++) + { + n += multi[i].getAttributeCount(); + } + return n; + } + + public Enumeration getAttributeNames() + { + return new MultiNameEnumeration(); + } + + public AttributeSet getResolveParent() + { + return null; + } + + public boolean isDefined(Object attrName) + { + boolean ret = false; + for (int i = 0; i < multi.length && ! ret; i++) + ret = multi[i].isDefined(attrName); + return ret; + } + + public boolean isEqual(AttributeSet attr) + { + return getAttributeCount() == attr.getAttributeCount() + && containsAttributes(attr); + } + +} diff --git a/libjava/classpath/javax/swing/text/html/MultiStyle.java b/libjava/classpath/javax/swing/text/html/MultiStyle.java new file mode 100644 index 00000000000..3937bff75a9 --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/MultiStyle.java @@ -0,0 +1,136 @@ +/* MultiStyle.java -- Multiplexes between several Styles + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import java.util.Enumeration; + +import javax.swing.event.ChangeListener; +import javax.swing.text.AttributeSet; +import javax.swing.text.SimpleAttributeSet; +import javax.swing.text.Style; + +/** + * A Style implementation that is able to multiplex between several other + * Styles. This is used for CSS style resolving. + * + * @author Roman Kennke (kennke@aicas.com) + */ +public class MultiStyle + extends MultiAttributeSet + implements Style +{ + + // FIXME: Fix the implementation to also return attributes that + // are added to this style, etc. However, this is not really needed + // now for CSS, but would be nice for correctness. + + /** + * The name of the style. + */ + private String name; + + /** + * The attributes added to this style. + */ + private SimpleAttributeSet attributes; + + /** + * Creates a new instance. + * + * @param n the name + * @param m the styles to multiplex + */ + public MultiStyle(String n, AttributeSet[] m) + { + super(m); + name = n; + attributes = new SimpleAttributeSet(); + } + + /** + * Returns the name of the style. + * + * @return the name of the style + */ + public String getName() + { + return name; + } + + public void addChangeListener(ChangeListener listener) + { + // TODO: Implement. + } + + public void removeChangeListener(ChangeListener listener) + { + // TODO: Implement. + } + + public void addAttribute(Object name, Object value) + { + attributes.addAttribute(name, value); + } + + public void addAttributes(AttributeSet atts) + { + attributes.addAttributes(atts); + } + + public void removeAttribute(Object name) + { + attributes.removeAttribute(name); + } + + public void removeAttributes(Enumeration names) + { + attributes.removeAttribute(names); + } + + public void removeAttributes(AttributeSet atts) + { + attributes.removeAttribute(atts); + } + + public void setResolveParent(AttributeSet parent) + { + // TODO: Implement. + } + +} diff --git a/libjava/classpath/javax/swing/text/html/Option.java b/libjava/classpath/javax/swing/text/html/Option.java index 1def51b2f59..18d5c2bd86f 100644 --- a/libjava/classpath/javax/swing/text/html/Option.java +++ b/libjava/classpath/javax/swing/text/html/Option.java @@ -72,10 +72,10 @@ public class Option */ public Option(AttributeSet attr) { - attributes = attr; + // Protect the attribute set. + attributes = attr.copyAttributes(); label = null; - selected = false; - // FIXME: Probably initialize something using the attributes. + selected = attr.getAttribute(HTML.Attribute.SELECTED) != null; } /** @@ -151,7 +151,9 @@ public class Option */ public String getValue() { - // FIXME: Return some attribute here if specified. - return label; + String value = (String) attributes.getAttribute(HTML.Attribute.VALUE); + if (value == null) + value = label; + return value; } } diff --git a/libjava/classpath/javax/swing/text/html/ParagraphView.java b/libjava/classpath/javax/swing/text/html/ParagraphView.java index 2339f4e661d..d149627ff1c 100644 --- a/libjava/classpath/javax/swing/text/html/ParagraphView.java +++ b/libjava/classpath/javax/swing/text/html/ParagraphView.java @@ -38,13 +38,17 @@ exception statement from your version. */ package javax.swing.text.html; +import gnu.javax.swing.text.html.css.Length; + import java.awt.Graphics; +import java.awt.Rectangle; import java.awt.Shape; import javax.swing.SizeRequirements; import javax.swing.text.AttributeSet; import javax.swing.text.Document; import javax.swing.text.Element; +import javax.swing.text.StyleConstants; import javax.swing.text.View; /** @@ -55,10 +59,30 @@ import javax.swing.text.View; * @author Roman Kennke (kennke@aicas.com) */ public class ParagraphView - extends javax.swing.text.ParagraphView + extends javax.swing.text.ParagraphView { /** + * The attributes used by this view. + */ + private AttributeSet attributes; + + /** + * The stylesheet's box painter. + */ + private StyleSheet.BoxPainter painter; + + /** + * The width as specified in the stylesheet or null if not specified. + */ + private Length cssWidth; + + /** + * The height as specified in the stylesheet or null if not specified. + */ + private Length cssHeight; + + /** * Creates a new ParagraphView for the specified element. * * @param element the element @@ -88,8 +112,11 @@ public class ParagraphView */ public AttributeSet getAttributes() { - // FIXME: Implement this multiplexing thing. - return super.getAttributes(); + if (attributes == null) + { + attributes = getStyleSheet().getViewAttributes(this); + } + return attributes; } /** @@ -98,7 +125,44 @@ public class ParagraphView */ protected void setPropertiesFromAttributes() { - // FIXME: Implement this. + super.setPropertiesFromAttributes(); + + // Fetch CSS attributes. + attributes = getAttributes(); + if (attributes != null) + { + super.setPropertiesFromAttributes(); + Object o = attributes.getAttribute(CSS.Attribute.TEXT_ALIGN); + if (o != null) + { + String align = o.toString(); + if (align.equals("left")) + setJustification(StyleConstants.ALIGN_LEFT); + else if (align.equals("right")) + setJustification(StyleConstants.ALIGN_RIGHT); + else if (align.equals("center")) + setJustification(StyleConstants.ALIGN_CENTER); + else if (align.equals("justify")) + setJustification(StyleConstants.ALIGN_JUSTIFIED); + } + + // Fetch StyleSheet's box painter. + painter = getStyleSheet().getBoxPainter(attributes); + setInsets((short) painter.getInset(TOP, this), + (short) painter.getInset(LEFT, this), + (short) painter.getInset(BOTTOM, this), + (short) painter.getInset(RIGHT, this)); + + StyleSheet ss = getStyleSheet(); + float emBase = ss.getEMBase(attributes); + float exBase = ss.getEXBase(attributes); + cssWidth = (Length) attributes.getAttribute(CSS.Attribute.WIDTH); + if (cssWidth != null) + cssWidth.setFontBases(emBase, exBase); + cssHeight = (Length) attributes.getAttribute(CSS.Attribute.WIDTH); + if (cssHeight != null) + cssHeight.setFontBases(emBase, exBase); + } } /** @@ -129,8 +193,52 @@ public class ParagraphView protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) { - // FIXME: Implement the above specified behaviour. - return super.calculateMinorAxisRequirements(axis, r); + r = super.calculateMinorAxisRequirements(axis, r); + if (! setCSSSpan(r, axis)) + { + int margin = axis == X_AXIS ? getLeftInset() + getRightInset() + : getTopInset() + getBottomInset(); + r.minimum -= margin; + r.preferred -= margin; + r.maximum -= margin; + } + return r; + } + + /** + * Sets the span on the SizeRequirements object according to the + * according CSS span value, when it is set. + * + * @param r the size requirements + * @param axis the axis + * + * @return <code>true</code> when the CSS span has been set, + * <code>false</code> otherwise + */ + private boolean setCSSSpan(SizeRequirements r, int axis) + { + boolean ret = false; + if (axis == X_AXIS) + { + if (cssWidth != null && ! cssWidth.isPercentage()) + { + r.minimum = (int) cssWidth.getValue(); + r.preferred = (int) cssWidth.getValue(); + r.maximum = (int) cssWidth.getValue(); + ret = true; + } + } + else + { + if (cssHeight != null && ! cssWidth.isPercentage()) + { + r.minimum = (int) cssHeight.getValue(); + r.preferred = (int) cssHeight.getValue(); + r.maximum = (int) cssHeight.getValue(); + ret = true; + } + } + return ret; } /** @@ -147,15 +255,20 @@ public class ParagraphView } /** - * Paints this view. This delegates to the superclass after the coordinates - * have been updated for tab calculations. + * Paints this view. This paints the box using the stylesheet's + * box painter for this view and delegates to the super class paint() + * afterwards. * * @param g the graphics object * @param a the current allocation of this view */ public void paint(Graphics g, Shape a) { - // FIXME: Implement the above specified behaviour. + if (a != null) + { + Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); + painter.paint(g, r.x, r.y, r.width, r.height, this); + } super.paint(g, a); } diff --git a/libjava/classpath/javax/swing/text/html/ResetableModel.java b/libjava/classpath/javax/swing/text/html/ResetableModel.java new file mode 100644 index 00000000000..17f65b97d13 --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/ResetableModel.java @@ -0,0 +1,50 @@ +/* ResetableModel.java -- Form models that can be resetted + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +/** + * Form models that can be resetted implement this. + */ +interface ResetableModel +{ + /** + * Resets the model. + */ + void reset(); +} diff --git a/libjava/classpath/javax/swing/text/html/ResetablePlainDocument.java b/libjava/classpath/javax/swing/text/html/ResetablePlainDocument.java new file mode 100644 index 00000000000..6177f9b86cf --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/ResetablePlainDocument.java @@ -0,0 +1,82 @@ +/* ResetablePlainDocument.java -- A plain document for use in the HTML renderer + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import javax.swing.text.BadLocationException; +import javax.swing.text.PlainDocument; + +/** + * A PlainDocument that can be resetted. + */ +class ResetablePlainDocument + extends PlainDocument + implements ResetableModel +{ + /** + * The initial text. + */ + private String initial; + + /** + * Stores the initial text. + * + * @param text the initial text + */ + void setInitialText(String text) + { + initial = text; + } + + /** + * Resets the model. + */ + public void reset() + { + try + { + replace(0, getLength(), initial, null); + } + catch (BadLocationException ex) + { + // Shouldn't happen. + assert false; + } + } + +} diff --git a/libjava/classpath/javax/swing/text/html/ResetableToggleButtonModel.java b/libjava/classpath/javax/swing/text/html/ResetableToggleButtonModel.java new file mode 100644 index 00000000000..619c24e477c --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/ResetableToggleButtonModel.java @@ -0,0 +1,71 @@ +/* ResetableToggleButtonModel.java -- A toggle button model with reset support + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import javax.swing.ButtonGroup; +import javax.swing.JToggleButton.ToggleButtonModel; + +class ResetableToggleButtonModel + extends ToggleButtonModel + implements ResetableModel +{ + + /** + * The initial state. + */ + private boolean initial; + + /** + * Sets the initial selection value. + * + * @param state the initial value + */ + public void setInitial(boolean state) + { + initial = state; + } + + /** + * Resets the model. + */ + public void reset() + { + setSelected(initial); + } +} diff --git a/libjava/classpath/javax/swing/text/html/SelectComboBoxModel.java b/libjava/classpath/javax/swing/text/html/SelectComboBoxModel.java new file mode 100644 index 00000000000..999746413c8 --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/SelectComboBoxModel.java @@ -0,0 +1,84 @@ +/* SelectComboBoxModel.java -- A special ComboBoxModel for use in HTML renderer + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import javax.swing.DefaultComboBoxModel; + +/** + * A special ComboBoxModel that supports storing the initial value so that + * the combobox can be resetted later. + */ +class SelectComboBoxModel + extends DefaultComboBoxModel + implements ResetableModel +{ + + /** + * The initial selection. + */ + private Option initial; + + /** + * Sets the initial selection. + * + * @param option the initial selection + */ + void setInitialSelection(Option option) + { + initial = option; + } + + /** + * Returns the initial selection. + * + * @return the initial selection + */ + Option getInitialSelection() + { + return initial; + } + + /** + * Resets the model. + */ + public void reset() + { + setSelectedItem(initial); + } +} diff --git a/libjava/classpath/javax/swing/text/html/SelectListModel.java b/libjava/classpath/javax/swing/text/html/SelectListModel.java new file mode 100644 index 00000000000..23bfaa11b86 --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/SelectListModel.java @@ -0,0 +1,106 @@ +/* OptionListModel.java -- A special ListModel for use in the HTML renderer + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import java.util.BitSet; + +import javax.swing.DefaultListModel; +import javax.swing.DefaultListSelectionModel; +import javax.swing.ListSelectionModel; + +/** + * A special list model that encapsulates its selection model and supports + * storing of the initial value so that it can be resetted. + */ +class SelectListModel + extends DefaultListModel + implements ResetableModel +{ + /** + * The selection model. + */ + private DefaultListSelectionModel selectionModel; + + /** + * The initial selection. + */ + private BitSet initialSelection; + + /** + * Creates a new SelectListModel. + */ + SelectListModel() + { + selectionModel = new DefaultListSelectionModel(); + initialSelection = new BitSet(); + } + + /** + * Sets the initial selection. + * + * @param init the initial selection + */ + void addInitialSelection(int init) + { + initialSelection.set(init); + } + + /** + * Resets the model. + */ + public void reset() + { + selectionModel.clearSelection(); + for (int i = initialSelection.size(); i >= 0; i--) + { + if (initialSelection.get(i)) + selectionModel.addSelectionInterval(i, i); + } + } + + /** + * Returns the associated selection model. + * + * @return the associated selection model + */ + ListSelectionModel getSelectionModel() + { + return selectionModel; + } +} diff --git a/libjava/classpath/javax/swing/text/html/StyleSheet.java b/libjava/classpath/javax/swing/text/html/StyleSheet.java index d92abde7825..01f19fd7bdd 100644 --- a/libjava/classpath/javax/swing/text/html/StyleSheet.java +++ b/libjava/classpath/javax/swing/text/html/StyleSheet.java @@ -38,28 +38,47 @@ exception statement from your version. */ package javax.swing.text.html; -import gnu.javax.swing.text.html.CharacterAttributeTranslator; +import gnu.javax.swing.text.html.css.BorderWidth; +import gnu.javax.swing.text.html.css.CSSColor; +import gnu.javax.swing.text.html.css.CSSParser; +import gnu.javax.swing.text.html.css.CSSParserCallback; +import gnu.javax.swing.text.html.css.FontSize; +import gnu.javax.swing.text.html.css.FontStyle; +import gnu.javax.swing.text.html.css.FontWeight; +import gnu.javax.swing.text.html.css.Length; +import gnu.javax.swing.text.html.css.Selector; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; - +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.font.FontRenderContext; +import java.awt.geom.Rectangle2D; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.io.Reader; import java.io.Serializable; import java.io.StringReader; - -import java.net.MalformedURLException; import java.net.URL; - +import java.util.ArrayList; +import java.util.Collections; import java.util.Enumeration; -import java.util.Vector; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import javax.swing.border.Border; +import javax.swing.event.ChangeListener; import javax.swing.text.AttributeSet; import javax.swing.text.Element; import javax.swing.text.MutableAttributeSet; import javax.swing.text.SimpleAttributeSet; import javax.swing.text.Style; +import javax.swing.text.StyleConstants; import javax.swing.text.StyleContext; import javax.swing.text.View; @@ -79,21 +98,168 @@ import javax.swing.text.View; * * The rules are stored as named styles, and other information is stored to * translate the context of an element to a rule. - * + * * @author Lillian Angel (langel@redhat.com) */ public class StyleSheet extends StyleContext { + /** + * Parses CSS stylesheets using the parser in gnu/javax/swing/html/css. + * + * This is package private to avoid accessor methods. + */ + class CSSStyleSheetParserCallback + implements CSSParserCallback + { + /** + * The current styles. + */ + private CSSStyle[] styles; + + /** + * The precedence of the stylesheet to be parsed. + */ + private int precedence; + + /** + * Creates a new CSS parser. This parser parses a CSS stylesheet with + * the specified precedence. + * + * @param prec the precedence, according to the constants defined in + * CSSStyle + */ + CSSStyleSheetParserCallback(int prec) + { + precedence = prec; + } + + /** + * Called at the beginning of a statement. + * + * @param sel the selector + */ + public void startStatement(Selector[] sel) + { + styles = new CSSStyle[sel.length]; + for (int i = 0; i < sel.length; i++) + styles[i] = new CSSStyle(precedence, sel[i]); + } + + /** + * Called at the end of a statement. + */ + public void endStatement() + { + for (int i = 0; i < styles.length; i++) + css.add(styles[i]); + styles = null; + } + + /** + * Called when a declaration is parsed. + * + * @param property the property + * @param value the value + */ + public void declaration(String property, String value) + { + CSS.Attribute cssAtt = CSS.getAttribute(property); + Object val = CSS.getValue(cssAtt, value); + for (int i = 0; i < styles.length; i++) + { + CSSStyle style = styles[i]; + CSS.addInternal(style, cssAtt, value); + if (cssAtt != null) + style.addAttribute(cssAtt, val); + } + } + + } + + /** + * Represents a style that is defined by a CSS rule. + */ + private class CSSStyle + extends SimpleAttributeSet + implements Style, Comparable + { + + static final int PREC_UA = 0; + static final int PREC_NORM = 100000; + static final int PREC_AUTHOR_NORMAL = 200000; + static final int PREC_AUTHOR_IMPORTANT = 300000; + static final int PREC_USER_IMPORTANT = 400000; + + /** + * The priority of this style when matching CSS selectors. + */ + private int precedence; + + /** + * The selector for this rule. + * + * This is package private to avoid accessor methods. + */ + Selector selector; + + CSSStyle(int prec, Selector sel) + { + precedence = prec; + selector = sel; + } + + public String getName() + { + // TODO: Implement this for correctness. + return null; + } + + public void addChangeListener(ChangeListener listener) + { + // TODO: Implement this for correctness. + } + + public void removeChangeListener(ChangeListener listener) + { + // TODO: Implement this for correctness. + } + + /** + * Sorts the rule according to the style's precedence and the + * selectors specificity. + */ + public int compareTo(Object o) + { + CSSStyle other = (CSSStyle) o; + return other.precedence + other.selector.getSpecificity() + - precedence - selector.getSpecificity(); + } + + } + /** The base URL */ URL base; /** Base font size (int) */ int baseFontSize; - /** The style sheets stored. */ - StyleSheet[] styleSheet; - + /** + * The linked style sheets stored. + */ + private ArrayList linked; + + /** + * Maps element names (selectors) to AttributSet (the corresponding style + * information). + */ + ArrayList css = new ArrayList(); + + /** + * Maps selectors to their resolved styles. + */ + private HashMap resolvedStyles; + /** * Constructs a StyleSheet. */ @@ -101,6 +267,7 @@ public class StyleSheet extends StyleContext { super(); baseFontSize = 4; // Default font size from CSS + resolvedStyles = new HashMap(); } /** @@ -114,10 +281,198 @@ public class StyleSheet extends StyleContext */ public Style getRule(HTML.Tag t, Element e) { - // FIXME: Not implemented. - return null; + // Create list of the element and all of its parents, starting + // with the bottommost element. + ArrayList path = new ArrayList(); + Element el; + AttributeSet atts; + for (el = e; el != null; el = el.getParentElement()) + path.add(el); + + // Create fully qualified selector. + StringBuilder selector = new StringBuilder(); + int count = path.size(); + // We append the actual element after this loop. + for (int i = count - 1; i > 0; i--) + { + el = (Element) path.get(i); + atts = el.getAttributes(); + Object name = atts.getAttribute(StyleConstants.NameAttribute); + selector.append(name.toString()); + if (atts.isDefined(HTML.Attribute.ID)) + { + selector.append('#'); + selector.append(atts.getAttribute(HTML.Attribute.ID)); + } + if (atts.isDefined(HTML.Attribute.CLASS)) + { + selector.append('.'); + selector.append(atts.getAttribute(HTML.Attribute.CLASS)); + } + if (atts.isDefined(HTML.Attribute.DYNAMIC_CLASS)) + { + selector.append(':'); + selector.append(atts.getAttribute(HTML.Attribute.DYNAMIC_CLASS)); + } + if (atts.isDefined(HTML.Attribute.PSEUDO_CLASS)) + { + selector.append(':'); + selector.append(atts.getAttribute(HTML.Attribute.PSEUDO_CLASS)); + } + selector.append(' '); + } + selector.append(t.toString()); + el = (Element) path.get(0); + atts = el.getAttributes(); + // For leaf elements, we have to fetch the tag specific attributes. + if (el.isLeaf()) + { + Object o = atts.getAttribute(t); + if (o instanceof AttributeSet) + atts = (AttributeSet) o; + else + atts = null; + } + if (atts != null) + { + if (atts.isDefined(HTML.Attribute.ID)) + { + selector.append('#'); + selector.append(atts.getAttribute(HTML.Attribute.ID)); + } + if (atts.isDefined(HTML.Attribute.CLASS)) + { + selector.append('.'); + selector.append(atts.getAttribute(HTML.Attribute.CLASS)); + } + if (atts.isDefined(HTML.Attribute.DYNAMIC_CLASS)) + { + selector.append(':'); + selector.append(atts.getAttribute(HTML.Attribute.DYNAMIC_CLASS)); + } + if (atts.isDefined(HTML.Attribute.PSEUDO_CLASS)) + { + selector.append(':'); + selector.append(atts.getAttribute(HTML.Attribute.PSEUDO_CLASS)); + } + } + return getResolvedStyle(selector.toString(), path, t); } - + + /** + * Fetches a resolved style. If there is no resolved style for the + * specified selector, the resolve the style using + * {@link #resolveStyle(String, List, HTML.Tag)}. + * + * @param selector the selector for which to resolve the style + * @param path the Element path, used in the resolving algorithm + * @param tag the tag for which to resolve + * + * @return the resolved style + */ + private Style getResolvedStyle(String selector, List path, HTML.Tag tag) + { + Style style = (Style) resolvedStyles.get(selector); + if (style == null) + style = resolveStyle(selector, path, tag); + return style; + } + + /** + * Resolves a style. This creates arrays that hold the tag names, + * class and id attributes and delegates the work to + * {@link #resolveStyle(String, String[], Map[])}. + * + * @param selector the selector + * @param path the Element path + * @param tag the tag + * + * @return the resolved style + */ + private Style resolveStyle(String selector, List path, HTML.Tag tag) + { + int count = path.size(); + String[] tags = new String[count]; + Map[] attributes = new Map[count]; + for (int i = 0; i < count; i++) + { + Element el = (Element) path.get(i); + AttributeSet atts = el.getAttributes(); + if (i == 0 && el.isLeaf()) + { + Object o = atts.getAttribute(tag); + if (o instanceof AttributeSet) + atts = (AttributeSet) o; + else + atts = null; + } + if (atts != null) + { + HTML.Tag t = + (HTML.Tag) atts.getAttribute(StyleConstants.NameAttribute); + if (t != null) + tags[i] = t.toString(); + else + tags[i] = null; + attributes[i] = attributeSetToMap(atts); + } + else + { + tags[i] = null; + attributes[i] = null; + } + } + tags[0] = tag.toString(); + return resolveStyle(selector, tags, attributes); + } + + /** + * Performs style resolving. + * + * @param selector the selector + * @param tags the tags + * @param attributes the attributes of the tags + * + * @return the resolved style + */ + private Style resolveStyle(String selector, String[] tags, Map[] attributes) + { + // FIXME: This style resolver is not correct. But it works good enough for + // the default.css. + int count = tags.length; + ArrayList styles = new ArrayList(); + for (Iterator i = css.iterator(); i.hasNext();) + { + CSSStyle style = (CSSStyle) i.next(); + if (style.selector.matches(tags, attributes)) + styles.add(style); + } + + // Add styles from linked stylesheets. + if (linked != null) + { + for (int i = linked.size() - 1; i >= 0; i--) + { + StyleSheet ss = (StyleSheet) linked.get(i); + for (int j = ss.css.size() - 1; j >= 0; j--) + { + CSSStyle style = (CSSStyle) ss.css.get(j); + if (style.selector.matches(tags, attributes)) + styles.add(style); + } + } + } + + // Sort selectors. + Collections.sort(styles); + Style[] styleArray = new Style[styles.size()]; + styleArray = (Style[]) styles.toArray(styleArray); + Style resolved = new MultiStyle(selector, + (Style[]) styles.toArray(styleArray)); + resolvedStyles.put(selector, resolved); + return resolved; + } + /** * Gets the rule that best matches the selector. selector is a space * separated String of element names. The attributes of the returned @@ -128,27 +483,40 @@ public class StyleSheet extends StyleContext */ public Style getRule(String selector) { - // FIXME: Not implemented. - return null; + CSSStyle best = null; + for (Iterator i = css.iterator(); i.hasNext();) + { + CSSStyle style = (CSSStyle) i.next(); + if (style.compareTo(best) < 0) + best = style; + } + return best; } /** - * Adds a set if rules to the sheet. The rules are expected to be in valid + * Adds a set of rules to the sheet. The rules are expected to be in valid * CSS format. This is called as a result of parsing a <style> tag * * @param rule - the rule to add to the sheet */ public void addRule(String rule) { - CssParser cp = new CssParser(); + CSSStyleSheetParserCallback cb = + new CSSStyleSheetParserCallback(CSSStyle.PREC_AUTHOR_NORMAL); + // FIXME: Handle ref. + StringReader in = new StringReader(rule); + CSSParser parser = new CSSParser(in, cb); try - { - cp.parse(base, new StringReader(rule), false, false); - } - catch (IOException io) - { - // Do nothing here. - } + { + parser.parse(); + } + catch (IOException ex) + { + // Shouldn't happen. And if, then don't let it bork the outside code. + } + // Clean up resolved styles cache so that the new styles are recognized + // on next stylesheet request. + resolvedStyles.clear(); } /** @@ -176,10 +544,14 @@ public class StyleSheet extends StyleContext * parameter. * @throws IOException - For any IO error while reading */ - public void loadRules(Reader in, URL ref) throws IOException + public void loadRules(Reader in, URL ref) + throws IOException { - CssParser cp = new CssParser(); - cp.parse(ref, in, false, false); + CSSStyleSheetParserCallback cb = + new CSSStyleSheetParserCallback(CSSStyle.PREC_UA); + // FIXME: Handle ref. + CSSParser parser = new CSSParser(in, cb); + parser.parse(); } /** @@ -191,8 +563,7 @@ public class StyleSheet extends StyleContext */ public AttributeSet getViewAttributes(View v) { - // FIXME: Not implemented. - return null; + return new ViewAttributeSet(v, this); } /** @@ -215,11 +586,9 @@ public class StyleSheet extends StyleContext */ public void addStyleSheet(StyleSheet ss) { - if (styleSheet == null) - styleSheet = new StyleSheet[] {ss}; - else - System.arraycopy(new StyleSheet[] {ss}, 0, styleSheet, - styleSheet.length, 1); + if (linked == null) + linked = new ArrayList(); + linked.add(ss); } /** @@ -229,31 +598,9 @@ public class StyleSheet extends StyleContext */ public void removeStyleSheet(StyleSheet ss) { - if (styleSheet.length == 1 && styleSheet[0].equals(ss)) - styleSheet = null; - else + if (linked != null) { - for (int i = 0; i < styleSheet.length; i++) - { - StyleSheet curr = styleSheet[i]; - if (curr.equals(ss)) - { - StyleSheet[] tmp = new StyleSheet[styleSheet.length - 1]; - if (i != 0 && i != (styleSheet.length - 1)) - { - System.arraycopy(styleSheet, 0, tmp, 0, i); - System.arraycopy(styleSheet, i + 1, tmp, i, - styleSheet.length - i - 1); - } - else if (i == 0) - System.arraycopy(styleSheet, 1, tmp, 0, styleSheet.length - 1); - else - System.arraycopy(styleSheet, 0, tmp, 0, styleSheet.length - 1); - - styleSheet = tmp; - break; - } - } + linked.remove(ss); } } @@ -264,18 +611,41 @@ public class StyleSheet extends StyleContext */ public StyleSheet[] getStyleSheets() { - return styleSheet; + StyleSheet[] linkedSS; + if (linked != null) + { + linkedSS = new StyleSheet[linked.size()]; + linkedSS = (StyleSheet[]) linked.toArray(linkedSS); + } + else + { + linkedSS = null; + } + return linkedSS; } /** * Imports a style sheet from the url. The rules are directly added to the - * receiver. + * receiver. This is usually called when a <link> tag is resolved in an + * HTML document. * - * @param url - the URL to import the StyleSheet from. + * @param url the URL to import the StyleSheet from */ public void importStyleSheet(URL url) { - // FIXME: Not implemented + try + { + InputStream in = url.openStream(); + Reader r = new BufferedReader(new InputStreamReader(in)); + CSSStyleSheetParserCallback cb = + new CSSStyleSheetParserCallback(CSSStyle.PREC_AUTHOR_NORMAL); + CSSParser parser = new CSSParser(r, cb); + parser.parse(); + } + catch (IOException ex) + { + // We can't do anything about it I guess. + } } /** @@ -310,7 +680,9 @@ public class StyleSheet extends StyleContext public void addCSSAttribute(MutableAttributeSet attr, CSS.Attribute key, String value) { - attr.addAttribute(key, value); + Object val = CSS.getValue(key, value); + CSS.addInternal(attr, key, value); + attr.addAttribute(key, val); } /** @@ -340,8 +712,90 @@ public class StyleSheet extends StyleContext */ public AttributeSet translateHTMLToCSS(AttributeSet htmlAttrSet) { - // FIXME: Not implemented. - return null; + AttributeSet cssAttr = htmlAttrSet.copyAttributes(); + + // The HTML align attribute maps directly to the CSS text-align attribute. + Object o = htmlAttrSet.getAttribute(HTML.Attribute.ALIGN); + if (o != null) + cssAttr = addAttribute(cssAttr, CSS.Attribute.TEXT_ALIGN, o); + + // The HTML width attribute maps directly to CSS width. + o = htmlAttrSet.getAttribute(HTML.Attribute.WIDTH); + if (o != null) + cssAttr = addAttribute(cssAttr, CSS.Attribute.WIDTH, + new Length(o.toString())); + + // The HTML height attribute maps directly to CSS height. + o = htmlAttrSet.getAttribute(HTML.Attribute.HEIGHT); + if (o != null) + cssAttr = addAttribute(cssAttr, CSS.Attribute.HEIGHT, + new Length(o.toString())); + + o = htmlAttrSet.getAttribute(HTML.Attribute.NOWRAP); + if (o != null) + cssAttr = addAttribute(cssAttr, CSS.Attribute.WHITE_SPACE, "nowrap"); + + // Map cellspacing attr of tables to CSS border-spacing. + o = htmlAttrSet.getAttribute(HTML.Attribute.CELLSPACING); + if (o != null) + cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_SPACING, + new Length(o.toString())); + + // For table cells and headers, fetch the cellpadding value from the + // parent table and set it as CSS padding attribute. + HTML.Tag tag = (HTML.Tag) + htmlAttrSet.getAttribute(StyleConstants.NameAttribute); + if ((tag == HTML.Tag.TD || tag == HTML.Tag.TH) + && htmlAttrSet instanceof Element) + { + Element el = (Element) htmlAttrSet; + AttributeSet tableAttrs = el.getParentElement().getParentElement() + .getAttributes(); + o = tableAttrs.getAttribute(HTML.Attribute.CELLPADDING); + if (o != null) + { + Length l = new Length(o.toString()); + cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_BOTTOM, l); + cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_LEFT, l); + cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_RIGHT, l); + cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_TOP, l); + } + o = tableAttrs.getAttribute(HTML.Attribute.BORDER); + cssAttr = translateBorder(cssAttr, o); + } + + // Translate border attribute. + o = cssAttr.getAttribute(HTML.Attribute.BORDER); + cssAttr = translateBorder(cssAttr, o); + + // TODO: Add more mappings. + return cssAttr; + } + + /** + * Translates a HTML border attribute to a corresponding set of CSS + * attributes. + * + * @param cssAttr the original set of CSS attributes to add to + * @param o the value of the border attribute + * + * @return the new set of CSS attributes + */ + private AttributeSet translateBorder(AttributeSet cssAttr, Object o) + { + if (o != null) + { + BorderWidth l = new BorderWidth(o.toString()); + if (l.getValue() > 0) + { + cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_WIDTH, l); + cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_STYLE, + "solid"); + cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_COLOR, + new CSSColor("black")); + } + } + return cssAttr; } /** @@ -416,10 +870,10 @@ public class StyleSheet extends StyleContext * @param names - the attribute names * @return the update attribute set */ - public AttributeSet removeAttributes(AttributeSet old, Enumeration names) + public AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names) { // FIXME: Not implemented. - return super.removeAttributes(old, names); + return super.removeAttributes(old, names); } /** @@ -455,9 +909,95 @@ public class StyleSheet extends StyleContext */ public Font getFont(AttributeSet a) { - return super.getFont(a); + int realSize = getFontSize(a); + + // Decrement size for subscript and superscript. + Object valign = a.getAttribute(CSS.Attribute.VERTICAL_ALIGN); + if (valign != null) + { + String v = valign.toString(); + if (v.contains("sup") || v.contains("sub")) + realSize -= 2; + } + + // TODO: Convert font family. + String family = "SansSerif"; + + int style = Font.PLAIN; + FontWeight weight = (FontWeight) a.getAttribute(CSS.Attribute.FONT_WEIGHT); + if (weight != null) + style |= weight.getValue(); + FontStyle fStyle = (FontStyle) a.getAttribute(CSS.Attribute.FONT_STYLE); + if (fStyle != null) + style |= fStyle.getValue(); + return new Font(family, style, realSize); } - + + /** + * Determines the EM base value based on the specified attributes. + * + * @param atts the attibutes + * + * @return the EM base value + */ + float getEMBase(AttributeSet atts) + { + Font font = getFont(atts); + FontRenderContext ctx = new FontRenderContext(null, false, false); + Rectangle2D bounds = font.getStringBounds("M", ctx); + return (float) bounds.getWidth(); + } + + /** + * Determines the EX base value based on the specified attributes. + * + * @param atts the attibutes + * + * @return the EX base value + */ + float getEXBase(AttributeSet atts) + { + Font font = getFont(atts); + FontRenderContext ctx = new FontRenderContext(null, false, false); + Rectangle2D bounds = font.getStringBounds("x", ctx); + return (float) bounds.getHeight(); + } + + /** + * Resolves the fontsize for a given set of attributes. + * + * @param atts the attributes + * + * @return the resolved font size + */ + private int getFontSize(AttributeSet atts) + { + int size = 12; + if (atts.isDefined(CSS.Attribute.FONT_SIZE)) + { + FontSize fs = (FontSize) atts.getAttribute(CSS.Attribute.FONT_SIZE); + if (fs.isRelative()) + { + int parSize = 12; + AttributeSet resolver = atts.getResolveParent(); + if (resolver != null) + parSize = getFontSize(resolver); + size = fs.getValue(parSize); + } + else + { + size = fs.getValue(); + } + } + else + { + AttributeSet resolver = atts.getResolveParent(); + if (resolver != null) + size = getFontSize(resolver); + } + return size; + } + /** * Takes a set of attributes and turns it into a foreground * color specification. This is used to specify things like, brigher, more hue @@ -468,7 +1008,11 @@ public class StyleSheet extends StyleContext */ public Color getForeground(AttributeSet a) { - return super.getForeground(a); + CSSColor c = (CSSColor) a.getAttribute(CSS.Attribute.COLOR); + Color color = null; + if (c != null) + color = c.getValue(); + return color; } /** @@ -481,7 +1025,11 @@ public class StyleSheet extends StyleContext */ public Color getBackground(AttributeSet a) { - return super.getBackground(a); + CSSColor c = (CSSColor) a.getAttribute(CSS.Attribute.BACKGROUND_COLOR); + Color color = null; + if (c != null) + color = c.getValue(); + return color; } /** @@ -492,7 +1040,7 @@ public class StyleSheet extends StyleContext */ public BoxPainter getBoxPainter(AttributeSet a) { - return new BoxPainter(a); + return new BoxPainter(a, this); } /** @@ -503,7 +1051,7 @@ public class StyleSheet extends StyleContext */ public ListPainter getListPainter(AttributeSet a) { - return new ListPainter(a); + return new ListPainter(a, this); } /** @@ -595,7 +1143,7 @@ public class StyleSheet extends StyleContext */ public Color stringToColor(String colorName) { - return CharacterAttributeTranslator.getColor(colorName); + return CSSColor.convertValue(colorName); } /** @@ -609,22 +1157,112 @@ public class StyleSheet extends StyleContext */ public static class BoxPainter extends Object implements Serializable { - + /** - * Attribute set for painter + * The left inset. */ - AttributeSet as; - + private float leftInset; + + /** + * The right inset. + */ + private float rightInset; + + /** + * The top inset. + */ + private float topInset; + + /** + * The bottom inset. + */ + private float bottomInset; + + /** + * The border of the box. + */ + private Border border; + + private float leftPadding; + private float rightPadding; + private float topPadding; + private float bottomPadding; + + /** + * The background color. + */ + private Color background; + /** * Package-private constructor. * * @param as - AttributeSet for painter */ - BoxPainter(AttributeSet as) + BoxPainter(AttributeSet as, StyleSheet ss) { - this.as = as; + float emBase = ss.getEMBase(as); + float exBase = ss.getEXBase(as); + // Fetch margins. + Length l = (Length) as.getAttribute(CSS.Attribute.MARGIN_LEFT); + if (l != null) + { + l.setFontBases(emBase, exBase); + leftInset = l.getValue(); + } + l = (Length) as.getAttribute(CSS.Attribute.MARGIN_RIGHT); + if (l != null) + { + l.setFontBases(emBase, exBase); + rightInset = l.getValue(); + } + l = (Length) as.getAttribute(CSS.Attribute.MARGIN_TOP); + if (l != null) + { + l.setFontBases(emBase, exBase); + topInset = l.getValue(); + } + l = (Length) as.getAttribute(CSS.Attribute.MARGIN_BOTTOM); + if (l != null) + { + l.setFontBases(emBase, exBase); + bottomInset = l.getValue(); + } + + // Fetch padding. + l = (Length) as.getAttribute(CSS.Attribute.PADDING_LEFT); + if (l != null) + { + l.setFontBases(emBase, exBase); + leftPadding = l.getValue(); + } + l = (Length) as.getAttribute(CSS.Attribute.PADDING_RIGHT); + if (l != null) + { + l.setFontBases(emBase, exBase); + rightPadding = l.getValue(); + } + l = (Length) as.getAttribute(CSS.Attribute.PADDING_TOP); + if (l != null) + { + l.setFontBases(emBase, exBase); + topPadding = l.getValue(); + } + l = (Length) as.getAttribute(CSS.Attribute.PADDING_BOTTOM); + if (l != null) + { + l.setFontBases(emBase, exBase); + bottomPadding = l.getValue(); + } + + // Determine border. + border = new CSSBorder(as, ss); + + // Determine background. + background = ss.getBackground(as); + } + /** * Gets the inset needed on a given side to account for the margin, border * and padding. @@ -638,8 +1276,37 @@ public class StyleSheet extends StyleContext */ public float getInset(int size, View v) { - // FIXME: Not implemented. - return 0; + float inset; + switch (size) + { + case View.TOP: + inset = topInset; + if (border != null) + inset += border.getBorderInsets(null).top; + inset += topPadding; + break; + case View.BOTTOM: + inset = bottomInset; + if (border != null) + inset += border.getBorderInsets(null).bottom; + inset += bottomPadding; + break; + case View.LEFT: + inset = leftInset; + if (border != null) + inset += border.getBorderInsets(null).left; + inset += leftPadding; + break; + case View.RIGHT: + inset = rightInset; + if (border != null) + inset += border.getBorderInsets(null).right; + inset += rightPadding; + break; + default: + inset = 0.0F; + } + return inset; } /** @@ -655,7 +1322,19 @@ public class StyleSheet extends StyleContext */ public void paint(Graphics g, float x, float y, float w, float h, View v) { - // FIXME: Not implemented. + int inX = (int) (x + leftInset); + int inY = (int) (y + topInset); + int inW = (int) (w - leftInset - rightInset); + int inH = (int) (h - topInset - bottomInset); + if (background != null) + { + g.setColor(background); + g.fillRect(inX, inY, inW, inH); + } + if (border != null) + { + border.paintBorder(null, g, inX, inY, inW, inH); + } } } @@ -666,24 +1345,41 @@ public class StyleSheet extends StyleContext * * @author Lillian Angel (langel@redhat.com) */ - public static class ListPainter extends Object implements Serializable + public static class ListPainter implements Serializable { - + /** * Attribute set for painter */ - AttributeSet as; - + private AttributeSet attributes; + + /** + * The associated style sheet. + */ + private StyleSheet styleSheet; + + /** + * The bullet type. + */ + private String type; + /** * Package-private constructor. * * @param as - AttributeSet for painter */ - ListPainter(AttributeSet as) + ListPainter(AttributeSet as, StyleSheet ss) { - this.as = as; + attributes = as; + styleSheet = ss; + type = (String) as.getAttribute(CSS.Attribute.LIST_STYLE_TYPE); } - + + /** + * Cached rectangle re-used in the paint method below. + */ + private final Rectangle tmpRect = new Rectangle(); + /** * Paints the CSS list decoration according to the attributes given. * @@ -698,210 +1394,66 @@ public class StyleSheet extends StyleContext public void paint(Graphics g, float x, float y, float w, float h, View v, int item) { - // FIXME: Not implemented. - } - } - - /** - * The parser callback for the CSSParser. - */ - class CssParser implements CSSParser.CSSParserCallback - { - /** - * A vector of all the selectors. - * Each element is an array of all the selector tokens - * in a single rule. - */ - Vector selectors; - - /** A vector of all the selector tokens in a rule. */ - Vector selectorTokens; - - /** Name of the current property. */ - String propertyName; - - /** The set of CSS declarations */ - MutableAttributeSet declaration; - - /** - * True if parsing a declaration, that is the Reader will not - * contain a selector. - */ - boolean parsingDeclaration; - - /** True if the attributes are coming from a linked/imported style. */ - boolean isLink; - - /** The base URL */ - URL base; - - /** The parser */ - CSSParser parser; - - /** - * Constructor - */ - CssParser() - { - selectors = new Vector(); - selectorTokens = new Vector(); - parser = new CSSParser(); - base = StyleSheet.this.base; - declaration = new SimpleAttributeSet(); - } - - /** - * Parses the passed in CSS declaration into an AttributeSet. - * - * @param s - the declaration - * @return the set of attributes containing the property and value. - */ - public AttributeSet parseDeclaration(String s) - { - try - { - return parseDeclaration(new StringReader(s)); - } - catch (IOException e) - { - // Do nothing here. - } - return null; - } - - /** - * Parses the passed in CSS declaration into an AttributeSet. - * - * @param r - the reader - * @return the attribute set - * @throws IOException from the reader - */ - public AttributeSet parseDeclaration(Reader r) throws IOException - { - parse(base, r, true, false); - return declaration; - } - - /** - * Parse the given CSS stream - * - * @param base - the url - * @param r - the reader - * @param parseDec - True if parsing a declaration - * @param isLink - True if parsing a link - */ - public void parse(URL base, Reader r, boolean parseDec, boolean isLink) throws IOException - { - parsingDeclaration = parseDec; - this.isLink = isLink; - this.base = base; - - // flush out all storage - propertyName = null; - selectors.clear(); - selectorTokens.clear(); - declaration.removeAttributes(declaration); - - parser.parse(r, this, parseDec); - } - - /** - * Invoked when a valid @import is encountered, - * will call importStyleSheet if a MalformedURLException - * is not thrown in creating the URL. - * - * @param s - the string after @import - */ - public void handleImport(String s) - { - if (s != null) + // FIXME: This is a very simplistic list rendering. We still need + // to implement different bullet types (see type field) and custom + // bullets via images. + View itemView = v.getView(item); + AttributeSet viewAtts = itemView.getAttributes(); + Object tag = viewAtts.getAttribute(StyleConstants.NameAttribute); + // Only paint something here when the child view is an LI tag + // and the calling view is some of the list tags then). + if (tag != null && tag == HTML.Tag.LI) { - try + g.setColor(Color.BLACK); + int centerX = (int) (x - 12); + int centerY = -1; + // For paragraphs (almost all cases) center bullet vertically + // in the middle of the first line. + tmpRect.setBounds((int) x, (int) y, (int) w, (int) h); + if (itemView.getViewCount() > 0) { - if (s.startsWith("url(") && s.endsWith(")")) - s = s.substring(4, s.length() - 1); - if (s.indexOf("\"") >= 0) - s = s.replaceAll("\"",""); - - URL url = new URL(s); - if (url == null && base != null) - url = new URL(base, s); - - importStyleSheet(url); + View v1 = itemView.getView(0); + if (v1 instanceof ParagraphView && v1.getViewCount() > 0) + { + Shape a1 = itemView.getChildAllocation(0, tmpRect); + Rectangle r1 = a1 instanceof Rectangle ? (Rectangle) a1 + : a1.getBounds(); + ParagraphView par = (ParagraphView) v1; + Shape a = par.getChildAllocation(0, r1); + if (a != null) + { + Rectangle r = a instanceof Rectangle ? (Rectangle) a + : a.getBounds(); + centerY = (int) (r.height / 2 + r.y); + } + } } - catch (MalformedURLException e) + if (centerY == -1) { - // Do nothing here. + centerY =(int) (h / 2 + y); } + g.fillOval(centerX - 3, centerY - 3, 6, 6); } } + } - /** - * A selector has been encountered. - * - * @param s - a selector (e.g. P or UL or even P,) - */ - public void handleSelector(String s) - { - if (s.endsWith(",")) - s = s.substring(0, s.length() - 1); - - selectorTokens.addElement(s); - addSelector(); - } - - /** - * Invoked when the start of a rule is encountered. - */ - public void startRule() - { - addSelector(); - } - - /** - * Invoked when a property name is encountered. - * - * @param s - the property - */ - public void handleProperty(String s) - { - propertyName = s; - } - - /** - * Invoked when a property value is encountered. + /** + * Converts an AttributeSet to a Map. This is used for CSS resolving. * - * @param s - the value - */ - public void handleValue(String s) - { - // call addCSSAttribute - // FIXME: Not implemented - } - - /** - * Invoked when the end of a rule is encountered. - */ - public void endRule() - { - // FIXME: Not implemented - // add rules - propertyName = null; - } - - /** - * Adds the selector to the vector. - */ - private void addSelector() - { - int length = selectorTokens.size(); - if (length > 0) - { - Object[] sel = new Object[length]; - System.arraycopy(selectorTokens.toArray(), 0, sel, 0, length); - selectors.add(sel); - selectorTokens.clear(); - } - } + * @param atts the attributes to convert + * + * @return the converted map + */ + private Map attributeSetToMap(AttributeSet atts) + { + HashMap map = new HashMap(); + Enumeration keys = atts.getAttributeNames(); + while (keys.hasMoreElements()) + { + Object key = keys.nextElement(); + Object value = atts.getAttribute(key); + map.put(key.toString(), value.toString()); + } + return map; } } diff --git a/libjava/classpath/javax/swing/text/html/TableView.java b/libjava/classpath/javax/swing/text/html/TableView.java index c2edc8cdd64..f87d7b35fc5 100644 --- a/libjava/classpath/javax/swing/text/html/TableView.java +++ b/libjava/classpath/javax/swing/text/html/TableView.java @@ -38,49 +38,318 @@ exception statement from your version. */ package javax.swing.text.html; -import javax.swing.text.Document; +import java.awt.Graphics; +import java.awt.Rectangle; +import java.awt.Shape; + +import gnu.javax.swing.text.html.css.Length; + +import javax.swing.SizeRequirements; +import javax.swing.event.DocumentEvent; +import javax.swing.text.AttributeSet; import javax.swing.text.Element; +import javax.swing.text.StyleConstants; import javax.swing.text.View; import javax.swing.text.ViewFactory; /** - * A conrete implementation of TableView that renders HTML tables. - * - * @author Roman Kennke (kennke@aicas.com) + * A view implementation that renders HTML tables. + * + * This is basically a vertical BoxView that contains the rows of the table + * and the rows are horizontal BoxViews that contain the actual columns. */ class TableView - extends javax.swing.text.TableView + extends BlockView + implements ViewFactory { + /** * Represents a single table row. */ - public class RowView extends TableRow + class RowView + extends BlockView { /** - * Creates a new instance of the <code>RowView</code>. + * Has true at column positions where an above row's cell overlaps into + * this row. + */ + boolean[] overlap; + + /** + * Stores the row index of this row. + */ + int rowIndex; + + /** + * Creates a new RowView. * - * @param el the element for which to create a row view + * @param el the element for the row view + */ + RowView(Element el) + { + super(el, X_AXIS); + } + + public void replace(int offset, int len, View[] views) + { + gridValid = false; + super.replace(offset, len, views); + } + + /** + * Overridden to make rows not resizable along the Y axis. + */ + public float getMaximumSpan(int axis) + { + float span; + if (axis == Y_AXIS) + span = super.getPreferredSpan(axis); + else + span = Integer.MAX_VALUE; + return span; + } + + public float getMinimumSpan(int axis) + { + float span; + if (axis == X_AXIS) + span = totalColumnRequirements.minimum; + else + span = super.getMinimumSpan(axis); + return span; + } + + public float getPreferredSpan(int axis) + { + float span; + if (axis == X_AXIS) + span = totalColumnRequirements.preferred; + else + span = super.getPreferredSpan(axis); + return span; + } + + /** + * Calculates the overall size requirements for the row along the + * major axis. This will be the sum of the column requirements. + */ + protected SizeRequirements calculateMajorAxisRequirements(int axis, + SizeRequirements r) + { + if (r == null) + r = new SizeRequirements(); + int adjust = (columnRequirements.length + 1) * cellSpacing; + r.minimum = totalColumnRequirements.minimum + adjust; + r.preferred = totalColumnRequirements.preferred + adjust; + r.maximum = totalColumnRequirements.maximum + adjust; + r.alignment = 0.0F; + return r; + } + + /** + * Lays out the columns in this row. */ - public RowView(Element el) + protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, + int spans[]) { - super(el); + super.layoutMinorAxis(targetSpan, axis, offsets, spans); + + // Adjust columns that have rowSpan > 1. + int numCols = getViewCount(); + for (int i = 0; i < numCols; i++) + { + View v = getView(i); + if (v instanceof CellView) + { + CellView cell = (CellView) v; + if (cell.rowSpan > 1) + { + for (int r = 1; r < cell.rowSpan; r++) + { + spans[i] += TableView.this.getSpan(axis, rowIndex + r); + spans[i] += cellSpacing; + } + } + } + } } - + + /** + * Lays out the columns in this row. + */ + protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, + int spans[]) + { + updateGrid(); + int numCols = offsets.length; + int realColumn = 0; + int colCount = getViewCount(); + for (int i = 0; i < numColumns;) + { + if (! overlap[i] && realColumn < colCount) + { + View v = getView(realColumn); + if (v instanceof CellView) + { + CellView cv = (CellView) v; + offsets[realColumn] = columnOffsets[i]; + spans[realColumn] = 0; + for (int j = 0; j < cv.colSpan; j++, i++) + { + spans[realColumn] += columnSpans[i]; + if (j < cv.colSpan - 1) + spans[realColumn] += cellSpacing; + } + } + realColumn++; + } + else + { + i++; + } + } + } + } + /** - * Get the associated style sheet from the document. - * - * @return the associated style sheet. + * A view that renders HTML table cells (TD and TH tags). */ - protected StyleSheet getStyleSheet() + class CellView + extends BlockView + { + + /** + * The number of columns that this view spans. + */ + int colSpan; + + /** + * The number of rows that this cell spans. + */ + int rowSpan; + + /** + * Creates a new CellView for the specified element. + * + * @param el the element for which to create the colspan + */ + CellView(Element el) { - Document d = getElement().getDocument(); - if (d instanceof HTMLDocument) - return ((HTMLDocument) d).getStyleSheet(); - else - return null; - } + super(el, Y_AXIS); + } + + protected SizeRequirements calculateMajorAxisRequirements(int axis, + SizeRequirements r) + { + r = super.calculateMajorAxisRequirements(axis, r); + r.maximum = Integer.MAX_VALUE; + return r; + } + + /** + * Overridden to fetch the columnSpan attibute. + */ + protected void setPropertiesFromAttributes() + { + super.setPropertiesFromAttributes(); + colSpan = 1; + AttributeSet atts = getAttributes(); + Object o = atts.getAttribute(HTML.Attribute.COLSPAN); + if (o != null) + { + try + { + colSpan = Integer.parseInt(o.toString()); + } + catch (NumberFormatException ex) + { + // Couldn't parse the colspan, assume 1. + colSpan = 1; + } + } + rowSpan = 1; + o = atts.getAttribute(HTML.Attribute.ROWSPAN); + if (o != null) + { + try + { + rowSpan = Integer.parseInt(o.toString()); + } + catch (NumberFormatException ex) + { + // Couldn't parse the colspan, assume 1. + rowSpan = 1; + } + } + } } + + /** + * The attributes of this view. + */ + private AttributeSet attributes; + + /** + * The column requirements. + * + * Package private to avoid accessor methods. + */ + SizeRequirements[] columnRequirements; + + /** + * The overall requirements across all columns. + * + * Package private to avoid accessor methods. + */ + SizeRequirements totalColumnRequirements; + + /** + * The column layout, offsets. + * + * Package private to avoid accessor methods. + */ + int[] columnOffsets; + + /** + * The column layout, spans. + * + * Package private to avoid accessor methods. + */ + int[] columnSpans; + + /** + * The widths of the columns that have been explicitly specified. + */ + Length[] columnWidths; + + /** + * The total number of columns. + */ + int numColumns; + + /** + * The table width. + */ + private Length width; + + /** + * Indicates if the grid setup is ok. + */ + boolean gridValid = false; + + /** + * Additional space that is added _between_ table cells. + * + * This is package private to avoid accessor methods. + */ + int cellSpacing; + + /** + * A cached Rectangle object for reuse in paint(). + */ + private Rectangle tmpRect; + /** * Creates a new HTML table view for the specified element. * @@ -88,50 +357,619 @@ class TableView */ public TableView(Element el) { - super(el); + super(el, Y_AXIS); + totalColumnRequirements = new SizeRequirements(); + tmpRect = new Rectangle(); } - + /** - * Get the associated style sheet from the document. - * - * @return the associated style sheet. + * Implementation of the ViewFactory interface for creating the + * child views correctly. */ - protected StyleSheet getStyleSheet() + public View create(Element elem) { - Document d = getElement().getDocument(); - if (d instanceof HTMLDocument) - return ((HTMLDocument) d).getStyleSheet(); + View view = null; + AttributeSet atts = elem.getAttributes(); + Object name = atts.getAttribute(StyleConstants.NameAttribute); + AttributeSet pAtts = elem.getParentElement().getAttributes(); + Object pName = pAtts.getAttribute(StyleConstants.NameAttribute); + + if (name == HTML.Tag.TR && pName == HTML.Tag.TABLE) + view = new RowView(elem); + else if ((name == HTML.Tag.TD || name == HTML.Tag.TH) + && pName == HTML.Tag.TR) + view = new CellView(elem); + else if (name == HTML.Tag.CAPTION) + view = new ParagraphView(elem); else - return null; - } - + { + // If we haven't mapped the element, then fall back to the standard + // view factory. + View parent = getParent(); + if (parent != null) + { + ViewFactory vf = parent.getViewFactory(); + if (vf != null) + view = vf.create(elem); + } + } + return view; + } + + /** + * Returns this object as view factory so that we get our TR, TD, TH + * and CAPTION subelements created correctly. + */ + public ViewFactory getViewFactory() + { + return this; + } + + /** + * Returns the attributes of this view. This is overridden to provide + * the attributes merged with the CSS stuff. + */ + public AttributeSet getAttributes() + { + if (attributes == null) + attributes = getStyleSheet().getViewAttributes(this); + return attributes; + } + + /** + * Returns the stylesheet associated with this view. + * + * @return the stylesheet associated with this view + */ + protected StyleSheet getStyleSheet() + { + HTMLDocument doc = (HTMLDocument) getDocument(); + return doc.getStyleSheet(); + } + + /** + * Overridden to calculate the size requirements according to the + * columns distribution. + */ + protected SizeRequirements calculateMinorAxisRequirements(int axis, + SizeRequirements r) + { + updateGrid(); + calculateColumnRequirements(); + + // Calculate the horizontal requirements according to the superclass. + // This will return the maximum of the row's widths. + r = super.calculateMinorAxisRequirements(axis, r); + + // Try to set the CSS width if it fits. + if (width != null) + { + int w = (int) width.getValue(); + if (r.minimum < w) + r.minimum = w; + } + + // Adjust requirements when we have cell spacing. + int adjust = (columnRequirements.length + 1) * cellSpacing; + r.minimum += adjust; + r.preferred += adjust; + + // Apply the alignment. + AttributeSet atts = getAttributes(); + Object o = atts.getAttribute(CSS.Attribute.TEXT_ALIGN); + r.alignment = 0.0F; + if (o != null) + { + String al = o.toString(); + if (al.equals("left")) + r.alignment = 0.0F; + else if (al.equals("center")) + r.alignment = 0.5F; + else if (al.equals("right")) + r.alignment = 1.0F; + } + + // Make it not resize in the horizontal direction. + r.maximum = r.preferred; + return r; + } + + /** + * Overridden to perform the table layout before calling the super + * implementation. + */ + protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, + int[] spans) + { + updateGrid(); + + // Mark all rows as invalid along their minor axis to force correct + // layout of multi-row cells. + int n = getViewCount(); + for (int i = 0; i < n; i++) + { + View row = getView(i); + if (row instanceof RowView) + ((RowView) row).layoutChanged(axis); + } + + layoutColumns(targetSpan); + super.layoutMinorAxis(targetSpan, axis, offsets, spans); + } + + /** + * Calculates the size requirements for the columns. + */ + private void calculateColumnRequirements() + { + int numRows = getViewCount(); + totalColumnRequirements.minimum = 0; + totalColumnRequirements.preferred = 0; + totalColumnRequirements.maximum = 0; + + // In this first pass we find out a suitable total width to fit in + // all columns of all rows. + for (int r = 0; r < numRows; r++) + { + View rowView = getView(r); + int numCols; + if (rowView instanceof RowView) + numCols = ((RowView) rowView).getViewCount(); + else + numCols = 0; + + // We collect the normal (non-relative) column requirements in the + // total variable and the relative requirements in the relTotal + // variable. In the end we create the maximum of both to get the + // real requirements. + SizeRequirements total = new SizeRequirements(); + SizeRequirements relTotal = new SizeRequirements(); + float totalPercent = 0.F; + int realCol = 0; + for (int c = 0; c < numCols; c++) + { + View v = rowView.getView(c); + if (v instanceof CellView) + { + CellView cellView = (CellView) v; + int colSpan = cellView.colSpan; + if (colSpan > 1) + { + int cellMin = (int) cellView.getMinimumSpan(X_AXIS); + int cellPref = (int) cellView.getPreferredSpan(X_AXIS); + int cellMax = (int) cellView.getMaximumSpan(X_AXIS); + int currentMin = 0; + int currentPref = 0; + long currentMax = 0; + for (int i = 0; i < colSpan; i++) + { + SizeRequirements req = columnRequirements[realCol]; + currentMin += req.minimum; + currentPref += req.preferred; + currentMax += req.maximum; + } + int deltaMin = cellMin - currentMin; + int deltaPref = cellPref - currentPref; + int deltaMax = (int) (cellMax - currentMax); + // Distribute delta. + for (int i = 0; i < colSpan; i++) + { + SizeRequirements req = columnRequirements[realCol]; + if (deltaMin > 0) + req.minimum += deltaMin / colSpan; + if (deltaPref > 0) + req.preferred += deltaPref / colSpan; + if (deltaMax > 0) + req.maximum += deltaMax / colSpan; + if (columnWidths[realCol] == null + || ! columnWidths[realCol].isPercentage()) + { + total.minimum += req.minimum; + total.preferred += req.preferred; + total.maximum += req.maximum; + } + else + { + relTotal.minimum = + Math.max(relTotal.minimum, + (int) (req.minimum + * columnWidths[realCol].getValue())); + relTotal.preferred = + Math.max(relTotal.preferred, + (int) (req.preferred + * columnWidths[realCol].getValue())); + relTotal.maximum = + Math.max(relTotal.maximum, + (int) (req.maximum + * columnWidths[realCol].getValue())); + totalPercent += columnWidths[realCol].getValue(); + } + } + realCol += colSpan; + } + else + { + // Shortcut for colSpan == 1. + SizeRequirements req = columnRequirements[realCol]; + req.minimum = Math.max(req.minimum, + (int) cellView.getMinimumSpan(X_AXIS)); + req.preferred = Math.max(req.preferred, + (int) cellView.getPreferredSpan(X_AXIS)); + req.maximum = Math.max(req.maximum, + (int) cellView.getMaximumSpan(X_AXIS)); + if (columnWidths[realCol] == null + || ! columnWidths[realCol].isPercentage()) + { + total.minimum += columnRequirements[realCol].minimum; + total.preferred += + columnRequirements[realCol].preferred; + total.maximum += columnRequirements[realCol].maximum; + } + else + { + relTotal.minimum = + Math.max(relTotal.minimum, + (int) (req.minimum + / columnWidths[c].getValue())); + relTotal.preferred = + Math.max(relTotal.preferred, + (int) (req.preferred + / columnWidths[c].getValue())); + relTotal.maximum = + Math.max(relTotal.maximum, + (int) (req.maximum + / columnWidths[c].getValue())); + totalPercent += columnWidths[c].getValue(); + } + realCol += 1; + } + } + } + + // Update the total requirements as follows: + // 1. Multiply the absolute requirements with 1 - totalPercent. This + // gives the total requirements based on the wishes of the absolute + // cells. + // 2. Take the maximum of this value and the total relative + // requirements. Now we should have enough space for whatever cell + // in this column. + // 3. Take the maximum of this value and the previous maximum value. + total.minimum *= 1.F / (1.F - totalPercent); + total.preferred *= 1.F / (1.F - totalPercent); + total.maximum *= 1.F / (1.F - totalPercent); + + int rowTotalMin = Math.max(total.minimum, relTotal.minimum); + int rowTotalPref = Math.max(total.preferred, relTotal.preferred); + int rowTotalMax = Math.max(total.maximum, relTotal.maximum); + totalColumnRequirements.minimum = + Math.max(totalColumnRequirements.minimum, rowTotalMin); + totalColumnRequirements.preferred = + Math.max(totalColumnRequirements.preferred, rowTotalPref); + totalColumnRequirements.maximum = + Math.max(totalColumnRequirements.maximum, rowTotalMax); + } + + // Now we know what we want and can fix up the actual relative + // column requirements. + int numCols = columnRequirements.length; + for (int i = 0; i < numCols; i++) + { + if (columnWidths[i] != null) + { + columnRequirements[i].minimum = (int) + columnWidths[i].getValue(totalColumnRequirements.minimum); + columnRequirements[i].preferred = (int) + columnWidths[i].getValue(totalColumnRequirements.preferred); + columnRequirements[i].maximum = (int) + columnWidths[i].getValue(totalColumnRequirements.maximum); + } + } + } + /** - * Creates a view for a table row. - * - * @param el the element that represents the table row - * @return a view for rendering the table row - * (and instance of {@link RowView}). + * Lays out the columns. + * + * @param targetSpan the target span into which the table is laid out */ - protected TableRow createTableRow(Element el) + private void layoutColumns(int targetSpan) { - return new RowView(el); - } - + // Set the spans to the preferred sizes. Determine the space + // that we have to adjust the sizes afterwards. + long sumPref = 0; + int n = columnRequirements.length; + for (int i = 0; i < n; i++) + { + SizeRequirements col = columnRequirements[i]; + if (columnWidths[i] != null) + columnSpans[i] = (int) columnWidths[i].getValue(targetSpan); + else + columnSpans[i] = col.preferred; + sumPref += columnSpans[i]; + } + + // Try to adjust the spans so that we fill the targetSpan. + // For adjustments we have to use the targetSpan minus the cumulated + // cell spacings. + long diff = targetSpan - (n + 1) * cellSpacing - sumPref; + float factor = 0.0F; + int[] diffs = null; + if (diff != 0) + { + long total = 0; + diffs = new int[n]; + for (int i = 0; i < n; i++) + { + // Only adjust the width if we haven't set a column width here. + if (columnWidths[i] == null) + { + SizeRequirements col = columnRequirements[i]; + int span; + if (diff < 0) + { + span = col.minimum; + diffs[i] = columnSpans[i] - span; + } + else + { + span = col.maximum; + diffs[i] = span - columnSpans[i]; + } + total += span; + } + else + total += columnSpans[i]; + } + + float maxAdjust = Math.abs(total - sumPref); + factor = diff / maxAdjust; + factor = Math.min(factor, 1.0F); + factor = Math.max(factor, -1.0F); + } + + // Actually perform adjustments. + int totalOffs = cellSpacing; + for (int i = 0; i < n; i++) + { + columnOffsets[i] = totalOffs; + if (diff != 0) + { + float adjust = factor * diffs[i]; + columnSpans[i] += Math.round(adjust); + } + // Avoid overflow here. + totalOffs = (int) Math.min((long) totalOffs + (long) columnSpans[i] + + (long) cellSpacing, Integer.MAX_VALUE); + } + } + /** - * Loads the children of the Table. This completely bypasses the ViewFactory - * and creates instances of TableRow instead. + * Updates the arrays that contain the row and column data in response + * to a change to the table structure. * - * @param vf ignored + * Package private to avoid accessor methods. + */ + void updateGrid() + { + if (! gridValid) + { + AttributeSet atts = getAttributes(); + StyleSheet ss = getStyleSheet(); + float emBase = ss.getEMBase(atts); + float exBase = ss.getEXBase(atts); + int maxColumns = 0; + int numRows = getViewCount(); + for (int r = 0; r < numRows; r++) + { + View rowView = getView(r); + int numCols = 0; + if (rowView instanceof RowView) + { + int numCells = ((RowView) rowView).getViewCount(); + for (int i = 0; i < numCells; i++) + { + View v = rowView.getView(i); + if (v instanceof CellView) + numCols += ((CellView) v).colSpan; + } + } + maxColumns = Math.max(numCols, maxColumns); + } + numColumns = maxColumns; + columnWidths = new Length[maxColumns]; + int[] rowSpans = new int[maxColumns]; + for (int r = 0; r < numRows; r++) + { + View view = getView(r); + if (view instanceof RowView) + { + RowView rowView = (RowView) view; + rowView.rowIndex = r; + rowView.overlap = new boolean[maxColumns]; + int colIndex = 0; + int colCount = rowView.getViewCount(); + for (int c = 0; c < maxColumns;) + { + if (rowSpans[c] > 0) + { + rowSpans[c]--; + rowView.overlap[c] = true; + c++; + } + else if (colIndex < colCount) + { + View v = rowView.getView(colIndex); + colIndex++; + if (v instanceof CellView) + { + CellView cv = (CellView) v; + Object o = + cv.getAttributes().getAttribute(CSS.Attribute.WIDTH); + if (o != null && columnWidths[c] == null + && o instanceof Length) + { + columnWidths[c]= (Length) o; + columnWidths[c].setFontBases(emBase, exBase); + } + int rs = cv.rowSpan - 1; + for (int col = cv.colSpan - 1; col >= 0; col--) + { + rowSpans[c] = rs; + c++; + } + } + } + else + { + c++; + } + } + } + } + columnRequirements = new SizeRequirements[maxColumns]; + for (int i = 0; i < maxColumns; i++) + columnRequirements[i] = new SizeRequirements(); + columnOffsets = new int[maxColumns]; + columnSpans = new int[maxColumns]; + + gridValid = true; + } + } + + /** + * Overridden to restrict the table width to the preferred size. + */ + public float getMaximumSpan(int axis) + { + float span; + if (axis == X_AXIS) + span = super.getPreferredSpan(axis); + else + span = super.getMaximumSpan(axis); + return span; + } + + /** + * Overridden to fetch the CSS attributes when view gets connected. + */ + public void setParent(View parent) + { + super.setParent(parent); + if (parent != null) + setPropertiesFromAttributes(); + } + + /** + * Fetches CSS and HTML layout attributes. + */ + protected void setPropertiesFromAttributes() + { + super.setPropertiesFromAttributes(); + + // Fetch and parse cell spacing. + AttributeSet atts = getAttributes(); + StyleSheet ss = getStyleSheet(); + float emBase = ss.getEMBase(atts); + float exBase = ss.getEXBase(atts); + Object o = atts.getAttribute(CSS.Attribute.BORDER_SPACING); + if (o != null && o instanceof Length) + { + Length l = (Length) o; + l.setFontBases(emBase, exBase); + cellSpacing = (int) l.getValue(); + } + o = atts.getAttribute(CSS.Attribute.WIDTH); + if (o != null && o instanceof Length) + { + width = (Length) o; + width.setFontBases(emBase, exBase); + } + } + + /** + * Overridden to adjust for cellSpacing. + */ + protected SizeRequirements calculateMajorAxisRequirements(int axis, + SizeRequirements r) + { + r = super.calculateMajorAxisRequirements(axis, r); + int adjust = (getViewCount() + 1) * cellSpacing; + r.minimum += adjust; + r.preferred += adjust; + r.maximum += adjust; + return r; + } + + /** + * Overridden to adjust for cellSpacing. + */ + protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, + int spans[]) + { + // Mark all rows as invalid along their minor axis to force correct + // layout of multi-row cells. + int n = getViewCount(); + for (int i = 0; i < n; i++) + { + View row = getView(i); + if (row instanceof RowView) + ((RowView) row).layoutChanged(axis); + } + + int adjust = (getViewCount() + 1) * cellSpacing; + super.layoutMajorAxis(targetSpan - adjust, axis, offsets, spans); + for (int i = 0; i < offsets.length; i++) + { + offsets[i] += (i + 1) * cellSpacing; + } + } + + /** + * Overridden to replace view factory with this one. + */ + public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) + { + super.insertUpdate(e, a, this); + } + + /** + * Overridden to replace view factory with this one. + */ + public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) + { + super.removeUpdate(e, a, this); + } + + /** + * Overridden to replace view factory with this one. */ - protected void loadChildren(ViewFactory vf) + public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) { - Element el = getElement(); - int numChildren = el.getElementCount(); - View[] rows = new View[numChildren]; - for (int i = 0; i < numChildren; ++i) + super.changedUpdate(e, a, this); + } + + public void replace(int offset, int len, View[] views) + { + gridValid = false; + super.replace(offset, len, views); + } + + /** + * We can't use the super class's paint() method because it might cut + * off multi-row children. Instead we trigger painting for all rows + * and let the rows sort out what to paint and what not. + */ + public void paint(Graphics g, Shape a) + { + Rectangle rect = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); + painter.paint(g, rect.x, rect.y, rect.width, rect.height, this); + int nRows = getViewCount(); + Rectangle inside = getInsideAllocation(a); + for (int r = 0; r < nRows; r++) { - rows[i] = createTableRow(el.getElement(i)); + tmpRect.setBounds(inside); + childAllocation(r, tmpRect); + paintChild(g, tmpRect, r); } - replace(0, getViewCount(), rows); } + } diff --git a/libjava/classpath/javax/swing/text/html/ViewAttributeSet.java b/libjava/classpath/javax/swing/text/html/ViewAttributeSet.java new file mode 100644 index 00000000000..25db89fc405 --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/ViewAttributeSet.java @@ -0,0 +1,163 @@ +/* ViewAttributeSet.java -- The AttributeSet used by HTML views + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import java.util.ArrayList; +import java.util.Enumeration; + +import javax.swing.text.AttributeSet; +import javax.swing.text.Element; +import javax.swing.text.StyleConstants; +import javax.swing.text.View; + +/** + * An AttributeSet implemenation that is used by the HTML views. This + * AttributeSet is created by StyleSheet.getViewAttributes() and combines + * the following attributes: + * - The original attributes of the View's element. + * - Any translated (HTML->CSS) attributes, as returned by + * StyleSheet.translateHTMLToCS(). + * - CSS Styles as resolved by the CSS stylesheet. + * + * In addition to that, it resolves attributes to the parent views, if + * a CSS attribute is requested that is inheritable. + * + * @author Roman Kennke (kennke@aicas.com) + */ +class ViewAttributeSet + extends MultiAttributeSet +{ + + /** + * The view for which we are the AttributeSet. + */ + private View view; + + /** + * The stylesheet to use. + */ + private StyleSheet styleSheet; + + /** + * Creates a new instance. + * + * @param v the view for which to do the AttributeSet + */ + ViewAttributeSet(View v, StyleSheet ss) + { + styleSheet = ss; + view = v; + ArrayList atts = new ArrayList(); + + Element el = v.getElement(); + AttributeSet elAtts = el.getAttributes(); + AttributeSet htmlAtts = styleSheet.translateHTMLToCSS(elAtts); + if (htmlAtts.getAttributeCount() > 0) + atts.add(htmlAtts); + + if (el.isLeaf()) + { + Enumeration n = elAtts.getAttributeNames(); + while (n.hasMoreElements()) + { + Object key = n.nextElement(); + if (key instanceof HTML.Tag) + { + AttributeSet rule = styleSheet.getRule((HTML.Tag) key, el); + if (rule != null) + atts.add(rule); + } + } + } + else + { + HTML.Tag tag = + (HTML.Tag) elAtts.getAttribute(StyleConstants.NameAttribute); + AttributeSet rule = styleSheet.getRule(tag, el); + if (rule != null) + atts.add(rule); + } + + AttributeSet[] atts1 = new AttributeSet[atts.size()]; + atts1 = (AttributeSet[]) atts.toArray(atts1); + init(atts1); + } + + /** + * Fetches the attribute for the specific ckey. If the attribute + * can't be found and the key is a CSS.Attribute that is inherited, + * then the attribute is looked up in the resolve parent. + */ + public Object getAttribute(Object key) + { + Object val = super.getAttribute(key); + if (val == null) + { + // Didn't find value. If the key is a CSS.Attribute, and is + // inherited, then ask the resolve parent. + if (key instanceof CSS.Attribute) + { + CSS.Attribute cssKey = (CSS.Attribute) key; + if (cssKey.isInherited()) + { + AttributeSet resolveParent = getResolveParent(); + if (resolveParent != null) + val = resolveParent.getAttribute(cssKey); + } + } + } + return val; + } + + /** + * Returns the resolve parent of this AttributeSet. This is the AttributeSet + * returned by the parent view if available. + */ + public AttributeSet getResolveParent() + { + AttributeSet parent = null; + if (view != null) + { + View parentView = view.getParent(); + if (parentView != null) + parent = parentView.getAttributes(); + } + return parent; + } +} diff --git a/libjava/classpath/javax/swing/text/html/parser/AttributeList.java b/libjava/classpath/javax/swing/text/html/parser/AttributeList.java index 5bca0bfa7db..d48266d4730 100644 --- a/libjava/classpath/javax/swing/text/html/parser/AttributeList.java +++ b/libjava/classpath/javax/swing/text/html/parser/AttributeList.java @@ -122,7 +122,7 @@ public final class AttributeList * null, if this parameter was not specified. * Values, defined in DTD, are case insensitive. */ - public Vector values; + public Vector<?> values; /** * The modifier of this attribute. This field contains one of the @@ -176,7 +176,7 @@ public final class AttributeList * Equals to null for the last attribute definition. */ public AttributeList(String a_name, int a_type, int a_modifier, - String a_default, Vector allowed_values, + String a_default, Vector<?> allowed_values, AttributeList a_next ) { @@ -251,7 +251,7 @@ public final class AttributeList /** * Get the allowed values of this attribute. */ - public Enumeration getValues() + public Enumeration<?> getValues() { return values.elements(); } diff --git a/libjava/classpath/javax/swing/text/html/parser/ContentModel.java b/libjava/classpath/javax/swing/text/html/parser/ContentModel.java index 70e9c2acbff..d5c4418de27 100644 --- a/libjava/classpath/javax/swing/text/html/parser/ContentModel.java +++ b/libjava/classpath/javax/swing/text/html/parser/ContentModel.java @@ -151,13 +151,15 @@ public final class ContentModel * discarded. * @param elements - a vector to add the values to. */ - public void getElements(Vector elements) + public void getElements(Vector<Element> elements) { ContentModel c = this; while (c != null) { - elements.add(c.content); + // FIXME: correct? + if (c.content instanceof Element) + elements.add((Element) c.content); c = c.next; } } diff --git a/libjava/classpath/javax/swing/text/html/parser/DTD.java b/libjava/classpath/javax/swing/text/html/parser/DTD.java index 16bc5b0d6af..ae3c184f153 100644 --- a/libjava/classpath/javax/swing/text/html/parser/DTD.java +++ b/libjava/classpath/javax/swing/text/html/parser/DTD.java @@ -88,7 +88,7 @@ public class DTD /** * The table of existing available DTDs. */ - static Hashtable dtdHash = new Hashtable(); + static Hashtable<String,DTD> dtdHash = new Hashtable<String,DTD>(); /** * The applet element for this DTD. @@ -148,12 +148,13 @@ public class DTD /** * The element for accessing all DTD elements by name. */ - public Hashtable elementHash = new Hashtable(); + public Hashtable<String,Element> elementHash = + new Hashtable<String,Element>(); /** * The entity table for accessing all DTD entities by name. */ - public Hashtable entityHash = new Hashtable(); + public Hashtable<Object, Entity> entityHash = new Hashtable<Object, Entity>(); /** * The name of this DTD. @@ -165,7 +166,7 @@ public class DTD * javax.swing.text.html.parser.Element#index field of all elements * in this vector is set to the element position in this vector. */ - public Vector elements = new Vector(); + public Vector<Element> elements = new Vector<Element>(); /** Create a new DTD with the specified name. */ protected DTD(String a_name) @@ -224,7 +225,7 @@ public class DTD String name = Entity.mapper.get(id); if (name != null) - return (Entity) entityHash.get(name); + return entityHash.get(name); else return null; } @@ -269,7 +270,7 @@ public class DTD */ public void defineAttributes(String forElement, AttributeList attributes) { - Element e = (Element) elementHash.get(forElement.toLowerCase()); + Element e = elementHash.get(forElement.toLowerCase()); if (e == null) e = newElement(forElement); @@ -420,7 +421,7 @@ public class DTD if (allowed_values != null) { StringTokenizer st = new StringTokenizer(allowed_values, " \t|"); - Vector v = new Vector(st.countTokens()); + Vector<String> v = new Vector<String>(st.countTokens()); while (st.hasMoreTokens()) v.add(st.nextToken()); @@ -571,7 +572,7 @@ public class DTD */ private Element newElement(String name) { - Element e = (Element) elementHash.get(name.toLowerCase()); + Element e = elementHash.get(name.toLowerCase()); if (e == null) { diff --git a/libjava/classpath/javax/swing/text/html/parser/DocumentParser.java b/libjava/classpath/javax/swing/text/html/parser/DocumentParser.java index 062606d17ba..f717d69cbda 100644 --- a/libjava/classpath/javax/swing/text/html/parser/DocumentParser.java +++ b/libjava/classpath/javax/swing/text/html/parser/DocumentParser.java @@ -38,13 +38,13 @@ exception statement from your version. */ package javax.swing.text.html.parser; -import gnu.javax.swing.text.html.parser.htmlAttributeSet; import javax.swing.text.html.parser.Parser; import java.io.IOException; import java.io.Reader; import javax.swing.text.BadLocationException; +import javax.swing.text.SimpleAttributeSet; import javax.swing.text.html.HTMLEditorKit; /** @@ -117,7 +117,7 @@ public class DocumentParser protected final void handleStartTag(TagElement tag) { parser.handleStartTag(tag); - htmlAttributeSet attributes = gnu.getAttributes(); + SimpleAttributeSet attributes = gnu.getAttributes(); if (tag.fictional()) attributes.addAttribute(HTMLEditorKit.ParserCallback.IMPLIED, diff --git a/libjava/classpath/javax/swing/text/html/parser/ParserDelegator.java b/libjava/classpath/javax/swing/text/html/parser/ParserDelegator.java index 70636d92923..cdd339b8f21 100644 --- a/libjava/classpath/javax/swing/text/html/parser/ParserDelegator.java +++ b/libjava/classpath/javax/swing/text/html/parser/ParserDelegator.java @@ -38,13 +38,13 @@ exception statement from your version. */ package javax.swing.text.html.parser; import gnu.javax.swing.text.html.parser.HTML_401F; -import gnu.javax.swing.text.html.parser.htmlAttributeSet; import java.io.IOException; import java.io.Reader; import java.io.Serializable; import javax.swing.text.BadLocationException; +import javax.swing.text.SimpleAttributeSet; import javax.swing.text.html.HTMLEditorKit; import javax.swing.text.html.HTMLEditorKit.ParserCallback; @@ -93,7 +93,7 @@ public class ParserDelegator protected final void handleStartTag(TagElement tag) { - htmlAttributeSet attributes = gnu.getAttributes(); + SimpleAttributeSet attributes = gnu.getAttributes(); if (tag.fictional()) attributes.addAttribute(ParserCallback.IMPLIED, Boolean.TRUE); |