| /* BlockView.java -- | |
| Copyright (C) 2005 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.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; | |
| 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; | |
| /** | |
| * @author Lillian Angel <langel@redhat.com> | |
| */ | |
| 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. | |
| * | |
| * @param elem - the element to create a view for | |
| * @param axis - either View.X_AXIS or View.Y_AXIS | |
| */ | |
| 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. | |
| * Implemented to forward to the superclass and call | |
| * setPropertiesFromAttributes to set the paragraph | |
| * properties. | |
| * | |
| * @param parent - the new parent, or null if the view | |
| * is being removed from a parent it was added to. | |
| */ | |
| public void setParent(View parent) | |
| { | |
| super.setParent(parent); | |
| if (parent != null) | |
| setPropertiesFromAttributes(); | |
| } | |
| /** | |
| * Calculates the requirements along the major axis. | |
| * This is implemented to call the superclass and then | |
| * adjust it if the CSS width or height attribute is specified | |
| * and applicable. | |
| * | |
| * @param axis - the axis to check the requirements for. | |
| * @param r - the SizeRequirements. If null, one is created. | |
| * @return the new SizeRequirements object. | |
| */ | |
| protected SizeRequirements calculateMajorAxisRequirements(int axis, | |
| SizeRequirements r) | |
| { | |
| 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 | |
| * adjust it if the CSS width or height attribute is specified | |
| * and applicable. | |
| * | |
| * @param axis - the axis to check the requirements for. | |
| * @param r - the SizeRequirements. If null, one is created. | |
| * @return the new SizeRequirements object. | |
| */ | |
| protected SizeRequirements calculateMinorAxisRequirements(int axis, | |
| SizeRequirements r) | |
| { | |
| 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 | |
| * of the layout are placed in the given arrays which are | |
| * the allocations to the children along the minor axis. | |
| * | |
| * @param targetSpan - the total span given to the view, also | |
| * used to layout the children. | |
| * @param axis - the minor axis | |
| * @param offsets - the offsets from the origin of the view for | |
| * all the child views. This is a return value and is filled in by this | |
| * function. | |
| * @param spans - the span of each child view. This is a return value and is | |
| * filled in by this function. | |
| */ | |
| protected void layoutMinorAxis(int targetSpan, int axis, | |
| int[] offsets, int[] 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 | |
| * border and background prior to the interior. | |
| * | |
| * @param g - Graphics configuration | |
| * @param a - the Shape to render into. | |
| */ | |
| public void paint(Graphics g, Shape a) | |
| { | |
| 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. | |
| * | |
| * @return the attributes of this model. | |
| */ | |
| public AttributeSet getAttributes() | |
| { | |
| if (attributes == null) | |
| attributes = getStyleSheet().getViewAttributes(this); | |
| return attributes; | |
| } | |
| /** | |
| * Gets the resize weight. | |
| * | |
| * @param axis - the axis to get the resize weight for. | |
| * @return the resize weight. | |
| * @throws IllegalArgumentException - for an invalid axis | |
| */ | |
| public int getResizeWeight(int axis) throws IllegalArgumentException | |
| { | |
| // Can't resize the Y_AXIS | |
| if (axis == Y_AXIS) | |
| return 0; | |
| if (axis == X_AXIS) | |
| return 1; | |
| throw new IllegalArgumentException("Invalid Axis"); | |
| } | |
| /** | |
| * Gets the alignment. | |
| * | |
| * @param axis - the axis to get the alignment for. | |
| * @return the alignment. | |
| */ | |
| public float getAlignment(int axis) | |
| { | |
| if (axis == X_AXIS) | |
| return super.getAlignment(axis); | |
| if (axis == Y_AXIS) | |
| { | |
| if (getViewCount() == 0) | |
| return 0.0F; | |
| float prefHeight = getPreferredSpan(Y_AXIS); | |
| 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"); | |
| } | |
| /** | |
| * Gives notification from the document that attributes were | |
| * changed in a location that this view is responsible for. | |
| * | |
| * @param ev - the change information | |
| * @param a - the current shape of the view | |
| * @param f - the factory to use to rebuild if the view has children. | |
| */ | |
| public void changedUpdate(DocumentEvent ev, | |
| Shape a, ViewFactory f) | |
| { | |
| super.changedUpdate(ev, a, f); | |
| // If more elements were added, then need to set the properties for them | |
| int currPos = ev.getOffset(); | |
| if (currPos <= getStartOffset() | |
| && (currPos + ev.getLength()) >= getEndOffset()) | |
| setPropertiesFromAttributes(); | |
| } | |
| /** | |
| * Determines the preferred span along the axis. | |
| * | |
| * @param axis - the view to get the preferred span for. | |
| * @return the span the view would like to be painted into >=0/ | |
| * The view is usually told to paint into the span that is returned, | |
| * although the parent may choose to resize or break the view. | |
| * @throws IllegalArgumentException - for an invalid axis | |
| */ | |
| public float getPreferredSpan(int axis) throws IllegalArgumentException | |
| { | |
| if (axis == X_AXIS || axis == Y_AXIS) | |
| return super.getPreferredSpan(axis); | |
| throw new IllegalArgumentException("Invalid Axis"); | |
| } | |
| /** | |
| * Determines the minimum span along the axis. | |
| * | |
| * @param axis - the axis to get the minimum span for. | |
| * @return the span the view would like to be painted into >=0/ | |
| * The view is usually told to paint into the span that is returned, | |
| * although the parent may choose to resize or break the view. | |
| * @throws IllegalArgumentException - for an invalid axis | |
| */ | |
| public float getMinimumSpan(int axis) throws IllegalArgumentException | |
| { | |
| if (axis == X_AXIS || axis == Y_AXIS) | |
| return super.getMinimumSpan(axis); | |
| throw new IllegalArgumentException("Invalid Axis"); | |
| } | |
| /** | |
| * Determines the maximum span along the axis. | |
| * | |
| * @param axis - the axis to get the maximum span for. | |
| * @return the span the view would like to be painted into >=0/ | |
| * The view is usually told to paint into the span that is returned, | |
| * although the parent may choose to resize or break the view. | |
| * @throws IllegalArgumentException - for an invalid axis | |
| */ | |
| public float getMaximumSpan(int axis) throws IllegalArgumentException | |
| { | |
| if (axis == X_AXIS || axis == Y_AXIS) | |
| return super.getMaximumSpan(axis); | |
| throw new IllegalArgumentException("Invalid Axis"); | |
| } | |
| /** | |
| * Updates any cached values that come from attributes. | |
| */ | |
| protected void setPropertiesFromAttributes() | |
| { | |
| // 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. | |
| * | |
| * @return the style sheet | |
| */ | |
| protected StyleSheet getStyleSheet() | |
| { | |
| 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); | |
| } | |
| } | |
| } | |
| } | |
| } |