FileDocCategorySizeDatePackage
ParagraphView.javaAPI DocJava SE 5 API32318Fri Aug 26 14:58:16 BST 2005javax.swing.text

ParagraphView

public class ParagraphView extends FlowView implements TabExpander
View of a simple line-wrapping paragraph that supports multiple fonts, colors, components, icons, etc. It is basically a vertical box with a margin around it. The contents of the box are a bunch of rows which are special horizontal boxes. This view creates a collection of views that represent the child elements of the paragraph element. Each of these views are placed into a row directly if they will fit, otherwise the breakView method is called to try and carve the view into pieces that fit.
author
Timothy Prinzing
author
Scott Violet
version
1.90 04/16/03
see
View

Fields Summary
private int
justification
private float
lineSpacing
protected int
firstLineIndent
Indentation for the first line, from the left inset.
private int
tabBase
Used by the TabExpander functionality to determine where to base the tab calculations. This is basically the location of the left side of the paragraph.
static Class
i18nStrategy
Used to create an i18n-based layout strategy
static char[]
tabChars
Used for searching for a tab.
static char[]
tabDecimalChars
Used for searching for a tab or decimal character.
Constructors Summary
public ParagraphView(Element elem)
Constructs a ParagraphView for the given element.

param
elem the element that this view is responsible for

	super(elem, View.Y_AXIS);
	setPropertiesFromAttributes();
	Document doc = elem.getDocument();
	Object i18nFlag = doc.getProperty(AbstractDocument.I18NProperty);
	if ((i18nFlag != null) && i18nFlag.equals(Boolean.TRUE)) {
	    try {
		if (i18nStrategy == null) {
		    // the classname should probably come from a property file.
		    String classname = "javax.swing.text.TextLayoutStrategy"; 
		    ClassLoader loader = getClass().getClassLoader();
		    if (loader != null) {
			i18nStrategy = loader.loadClass(classname);
		    } else {
			i18nStrategy = Class.forName(classname);
		    }
		}
		Object o = i18nStrategy.newInstance();
		if (o instanceof FlowStrategy) {
		    strategy = (FlowStrategy) o;
		}
	    } catch (Throwable e) {
		throw new StateInvariantError("ParagraphView: Can't create i18n strategy: " 
					      + e.getMessage());
	    }
	}
    
Methods Summary
protected voidadjustRow(javax.swing.text.ParagraphView$Row r, int desiredSpan, int x)
Adjusts the given row if possible to fit within the layout span. By default this will try to find the highest break weight possible nearest the end of the row. If a forced break is encountered, the break will be positioned there.

This is meant for internal usage, and should not be used directly.

param
r the row to adjust to the current layout span
param
desiredSpan the current layout span >= 0
param
x the location r starts at

    
public javax.swing.text.ViewbreakView(int axis, float len, java.awt.Shape a)
Breaks this view on the given axis at the given length.

ParagraphView instances are breakable along the Y_AXIS only, and only if len is after the first line.

param
axis may be either View.X_AXIS or View.Y_AXIS
param
len specifies where a potential break is desired along the given axis >= 0
param
a the current allocation of the view
return
the fragment of the view that represents the given span, if the view can be broken; if the view doesn't support breaking behavior, the view itself is returned
see
View#breakView

        if(axis == View.Y_AXIS) {
            if(a != null) {
                Rectangle alloc = a.getBounds();
                setSize(alloc.width, alloc.height);
            }
            // Determine what row to break on.

            // PENDING(prinz) add break support
            return this;
        }
        return this;
    
public voidchangedUpdate(javax.swing.event.DocumentEvent changes, java.awt.Shape a, javax.swing.text.ViewFactory f)
Gives notification from the document that attributes were changed in a location that this view is responsible for.

param
changes the change information from the associated document
param
a the current allocation of the view
param
f the factory to use to rebuild if the view has children
see
View#changedUpdate

        // update any property settings stored, and layout should be 
	// recomputed 
	setPropertiesFromAttributes();
	layoutChanged(X_AXIS);
	layoutChanged(Y_AXIS);
	super.changedUpdate(changes, a, f);
    
protected javax.swing.text.ViewcreateRow()
Create a View that should be used to hold a a row's worth of children in a flow.

return
the new View

	return new Row(getElement());
    
protected intfindOffsetToCharactersInString(char[] string, int start)
Finds the next character in the document with a character in string, starting at offset start. If there are no characters found, -1 will be returned.

param
string the string of characters
param
start where to start in the model >= 0
return
the document offset, or -1 if no characters found

        int stringLength = string.length;
        int end = getEndOffset();
        Segment seg = new Segment();
        try {
            getDocument().getText(start, end - start, seg);
        } catch (BadLocationException ble) {
            return -1;
        }
        for(int counter = seg.offset, maxCounter = seg.offset + seg.count;
            counter < maxCounter; counter++) {
            char currentChar = seg.array[counter];
            for(int subCounter = 0; subCounter < stringLength;
                subCounter++) {
                if(currentChar == string[subCounter])
                    return counter - seg.offset + start;
            }
        }
        // No match.
        return -1;
    
protected booleanflipEastAndWestAtEnds(int position, javax.swing.text.Position$Bias bias)
Determines in which direction the next view lays. Consider the View at index n. Typically the Views are layed out from left to right, so that the View to the EAST will be at index n + 1, and the View to the WEST will be at index n - 1. In certain situations, such as with bidirectional text, it is possible that the View to EAST is not at index n + 1, but rather at index n - 1, or that the View to the WEST is not at index n - 1, but index n + 1. In this case this method would return true, indicating the Views are layed out in descending order.

This will return true if the text is layed out right to left at position, otherwise false.

param
position position into the model
param
bias either Position.Bias.Forward or Position.Bias.Backward
return
true if the text is layed out right to left at position, otherwise false.

	Document doc = getDocument();
	if(doc instanceof AbstractDocument &&
	   !((AbstractDocument)doc).isLeftToRight(getStartOffset(),
						  getStartOffset() + 1)) {
	    return true;
	}
	return false;
    
public floatgetAlignment(int axis)
Determines the desired alignment for this view along an axis. This is implemented to give the alignment to the center of the first row along the y axis, and the default along the x axis.

param
axis may be either View.X_AXIS or View.Y_AXIS
return
the desired alignment. This should be a value between 0.0 and 1.0 inclusive, where 0 indicates alignment at the origin and 1.0 indicates alignment to the full span away from the origin. An alignment of 0.5 would be the center of the view.

        switch (axis) {
        case Y_AXIS:
	    float a = 0.5f;
	    if (getViewCount() != 0) {
		int paragraphSpan = (int) getPreferredSpan(View.Y_AXIS);
		View v = getView(0);
		int rowSpan = (int) v.getPreferredSpan(View.Y_AXIS);
		a = (paragraphSpan != 0) ? ((float)(rowSpan / 2)) / paragraphSpan : 0;
	    }
            return a;
	case X_AXIS:
	    return 0.5f;
	default:
            throw new IllegalArgumentException("Invalid axis: " + axis);
	}
    
public intgetBreakWeight(int axis, float len)
Gets the break weight for a given location.

ParagraphView instances are breakable along the Y_AXIS only, and only if len is after the first row. If the length is less than one row, a value of BadBreakWeight is returned.

param
axis may be either View.X_AXIS or View.Y_AXIS
param
len specifies where a potential break is desired >= 0
return
a value indicating the attractiveness of breaking here; either GoodBreakWeight or BadBreakWeight
see
View#getBreakWeight

        if(axis == View.Y_AXIS) {
            // PENDING(prinz) make this return a reasonable value
            // when paragraph breaking support is re-implemented.
            // If less than one row, bad weight value should be 
            // returned.
            //return GoodBreakWeight;
            return BadBreakWeight;
        }
        return BadBreakWeight;
    
protected intgetClosestPositionTo(int pos, javax.swing.text.Position$Bias b, java.awt.Shape a, int direction, javax.swing.text.Position$Bias[] biasRet, int rowIndex, int x)
Returns the closest model position to x. rowIndex gives the index of the view that corresponds that should be looked in.

param
pos position into the model
param
a the allocated region to render into
param
direction one of the following values:
  • SwingConstants.NORTH
  • SwingConstants.SOUTH
param
biasRet an array containing the bias that were checked in this method
param
rowIndex the index of the view
param
x the x coordinate of interest
return
the closest model position to x

        JTextComponent text = (JTextComponent)getContainer();
        Document doc = getDocument();
        AbstractDocument aDoc = (doc instanceof AbstractDocument) ?
                                (AbstractDocument)doc : null;
        View row = getView(rowIndex);
        int lastPos = -1;
        // This could be made better to check backward positions too.
        biasRet[0] = Position.Bias.Forward;
        for(int vc = 0, numViews = row.getViewCount(); vc < numViews; vc++) {
            View v = row.getView(vc);
            int start = v.getStartOffset();
            boolean ltr = (aDoc != null) ? aDoc.isLeftToRight
                           (start, start + 1) : true;
            if(ltr) {
                lastPos = start;
                for(int end = v.getEndOffset(); lastPos < end; lastPos++) {
                    float xx = text.modelToView(lastPos).getBounds().x;
                    if(xx >= x) {
                        while (++lastPos < end &&
                               text.modelToView(lastPos).getBounds().x == xx) {
                        }
                        return --lastPos;
                    }
                }
                lastPos--;
            }
            else {
                for(lastPos = v.getEndOffset() - 1; lastPos >= start;
                    lastPos--) {
                    float xx = text.modelToView(lastPos).getBounds().x;
                    if(xx >= x) {
                        while (--lastPos >= start &&
                               text.modelToView(lastPos).getBounds().x == xx) {
                        }
                        return ++lastPos;
                    }
                }
                lastPos++;
            }
        }
        if(lastPos == -1) {
            return getStartOffset();
        }
        return lastPos;
    
public intgetFlowSpan(int index)
Fetches the constraining span to flow against for the given child index.

param
index the index of the view being queried
return
the constraining span for the given view at index

	View child = getView(index);
	int adjust = 0;
	if (child instanceof Row) {
	    Row row = (Row) child;
	    adjust = row.getLeftInset() + row.getRightInset();
	}
	int span = layoutSpan - adjust;
	return span;
    
public intgetFlowStart(int index)
Fetches the location along the flow axis that the flow span will start at.

param
index the index of the view being queried
return
the location for the given view at index

	View child = getView(index);
	int adjust = 0;
	if (child instanceof Row) {
	    Row row = (Row) child;
	    adjust = row.getLeftInset();
	}
	return tabBase + adjust;
    
protected javax.swing.text.ViewgetLayoutView(int index)
Returns the view at a given index. The child views of the paragraph are rows which have been used to arrange pieces of the Views that represent the child elements. This methods returns the view responsible for the child element index (prior to breaking). These are the Views that were produced from a factory (to represent the child elements) and used for layout.

param
index the index of the desired view
return
the view at index

	return layoutPool.getView(index);
    
protected intgetLayoutViewCount()
Returns the number of views that this view is responsible for. The child views of the paragraph are rows which have been used to arrange pieces of the Views that represent the child elements. This is the number of views that have been tiled in two dimensions, and should be equivalent to the number of child elements to the element this view is responsible for.

return
the number of views that this ParagraphView is responsible for

	return layoutPool.getViewCount();
    
protected intgetNextNorthSouthVisualPositionFrom(int pos, javax.swing.text.Position$Bias b, java.awt.Shape a, int direction, javax.swing.text.Position$Bias[] biasRet)
Returns the next visual position for the cursor, in either the east or west direction. Overridden from CompositeView.

param
pos position into the model
param
b either Position.Bias.Forward or Position.Bias.Backward
param
a the allocated region to render into
param
direction either SwingConstants.NORTH or SwingConstants.SOUTH
param
biasRet an array containing the bias that were checked in this method
return
the location in the model that represents the next location visual position

	int vIndex;
	if(pos == -1) {
	    vIndex = (direction == NORTH) ?
		     getViewCount() - 1 : 0;
	}
	else {
	    if(b == Position.Bias.Backward && pos > 0) {
		vIndex = getViewIndexAtPosition(pos - 1);
	    }
	    else {
		vIndex = getViewIndexAtPosition(pos);
	    }
	    if(direction == NORTH) {
		if(vIndex == 0) {
		    return -1;
		}
		vIndex--;
	    }
	    else if(++vIndex >= getViewCount()) {
		return -1;
	    }
	}
	// vIndex gives index of row to look in.
	JTextComponent text = (JTextComponent)getContainer();
	Caret c = text.getCaret();
	Point magicPoint;
	magicPoint = (c != null) ? c.getMagicCaretPosition() : null;
	int x;
	if(magicPoint == null) {
	    Shape posBounds;
	    try {
		posBounds = text.getUI().modelToView(text, pos, b);
	    } catch (BadLocationException exc) {
		posBounds = null;
	    }
	    if(posBounds == null) {
		x = 0;
	    }
	    else {
		x = posBounds.getBounds().x;
	    }
	}
	else {
	    x = magicPoint.x;
	}
	return getClosestPositionTo(pos, b, a, direction, biasRet, vIndex, x);
    
protected floatgetPartialSize(int startOffset, int endOffset)
Returns the size used by the views between startOffset and endOffset. This uses getPartialView to calculate the size if the child view implements the TabableView interface. If a size is needed and a View does not implement the TabableView interface, the preferredSpan will be used.

param
startOffset the starting document offset >= 0
param
endOffset the ending document offset >= startOffset
return
the size >= 0

        float size = 0.0f;
        int viewIndex;
        int numViews = getViewCount();
        View view;
        int viewEnd;
        int tempEnd;

        // Have to search layoutPool!
        // PENDING: when ParagraphView supports breaking location
        // into layoutPool will have to change!
        viewIndex = getElement().getElementIndex(startOffset);
        numViews = layoutPool.getViewCount();
        while(startOffset < endOffset && viewIndex < numViews) {
            view = layoutPool.getView(viewIndex++);
            viewEnd = view.getEndOffset();
            tempEnd = Math.min(endOffset, viewEnd);
            if(view instanceof TabableView)
                size += ((TabableView)view).getPartialSpan(startOffset, tempEnd);
            else if(startOffset == view.getStartOffset() &&
                    tempEnd == view.getEndOffset())
                size += view.getPreferredSpan(View.X_AXIS);
            else
                // PENDING: should we handle this better?
                return 0.0f;
            startOffset = viewEnd;
        }
        return size;
    
protected floatgetTabBase()
Returns where the tabs are calculated from.

return
where tabs are calculated from

	return (float)tabBase;
    
protected javax.swing.text.TabSetgetTabSet()
Gets the Tabset to be used in calculating tabs.

return
the TabSet

	return StyleConstants.getTabSet(getElement().getAttributes());
    
public floatnextTabStop(float x, int tabOffset)
Returns the next tab stop position given a reference position. This view implements the tab coordinate system, and calls getTabbedSpan on the logical children in the process of layout to determine the desired span of the children. The logical children can delegate their tab expansion upward to the paragraph which knows how to expand tabs. LabelView is an example of a view that delegates its tab expansion needs upward to the paragraph.

This is implemented to try and locate a TabSet in the paragraph element's attribute set. If one can be found, its settings will be used, otherwise a default expansion will be provided. The base location for for tab expansion is the left inset from the paragraphs most recent allocation (which is what the layout of the children is based upon).

param
x the X reference position
param
tabOffset the position within the text stream that the tab occurred at >= 0
return
the trailing end of the tab expansion >= 0
see
TabSet
see
TabStop
see
LabelView

	// If the text isn't left justified, offset by 10 pixels!
	if(justification != StyleConstants.ALIGN_LEFT)
            return x + 10.0f;
        x -= tabBase;
        TabSet tabs = getTabSet();
        if(tabs == null) {
            // a tab every 72 pixels.
            return (float)(tabBase + (((int)x / 72 + 1) * 72));
        }
        TabStop tab = tabs.getTabAfter(x + .01f);
        if(tab == null) {
            // no tab, do a default of 5 pixels.
            // Should this cause a wrapping of the line?
            return tabBase + x + 5.0f;
        }
        int alignment = tab.getAlignment();
        int offset;
        switch(alignment) {
        default:
        case TabStop.ALIGN_LEFT:
            // Simple case, left tab.
            return tabBase + tab.getPosition();
        case TabStop.ALIGN_BAR:
            // PENDING: what does this mean?
            return tabBase + tab.getPosition();
        case TabStop.ALIGN_RIGHT:
        case TabStop.ALIGN_CENTER:
            offset = findOffsetToCharactersInString(tabChars,
                                                    tabOffset + 1);
            break;
        case TabStop.ALIGN_DECIMAL:
            offset = findOffsetToCharactersInString(tabDecimalChars,
                                                    tabOffset + 1);
            break;
        }
        if (offset == -1) {
            offset = getEndOffset();
        }
        float charsSize = getPartialSize(tabOffset + 1, offset);
        switch(alignment) {
        case TabStop.ALIGN_RIGHT:
        case TabStop.ALIGN_DECIMAL:
            // right and decimal are treated the same way, the new
            // position will be the location of the tab less the
            // partialSize.
            return tabBase + Math.max(x, tab.getPosition() - charsSize);
        case TabStop.ALIGN_CENTER: 
            // Similar to right, but half the partialSize.
            return tabBase + Math.max(x, tab.getPosition() - charsSize / 2.0f);
        }
        // will never get here!
        return x;
    
public voidpaint(java.awt.Graphics g, java.awt.Shape a)
Renders using the given rendering surface and area on that surface. This is implemented to delgate to the superclass after stashing the base coordinate for tab calculations.

param
g the rendering surface to use
param
a the allocated region to render into
see
View#paint

        Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
       	tabBase = alloc.x + getLeftInset();
	super.paint(g, a);

	// line with the negative firstLineIndent value needs
	// special handling
	if (firstLineIndent < 0) {
	    Shape sh = getChildAllocation(0, a);
	    if ((sh != null) &&  sh.intersects(alloc)) {
		int x = alloc.x + getLeftInset() + firstLineIndent;
		int y = alloc.y + getTopInset();                                 
                                                                              
		Rectangle clip = g.getClipBounds();                              
		tempRect.x = x + getOffset(X_AXIS, 0); 
		tempRect.y = y + getOffset(Y_AXIS, 0);                       
		tempRect.width = getSpan(X_AXIS, 0) - firstLineIndent;
		tempRect.height = getSpan(Y_AXIS, 0);                            
		if (tempRect.intersects(clip)) {                             
		    tempRect.x = tempRect.x - firstLineIndent;                
		    paintChild(g, tempRect, 0);                      
		}                            
	    }
	}
    
protected voidsetFirstLineIndent(float fi)
Sets the indent on the first line.

param
fi the value in points

	firstLineIndent = (int) fi;
    
protected voidsetJustification(int j)
Sets the type of justification.

param
j one of the following values:
  • StyleConstants.ALIGN_LEFT
  • StyleConstants.ALIGN_CENTER
  • StyleConstants.ALIGN_RIGHT

	justification = j;
    
protected voidsetLineSpacing(float ls)
Sets the line spacing.

param
ls the value is a factor of the line hight

	lineSpacing = ls;
    
protected voidsetPropertiesFromAttributes()
Set the cached properties from the attributes.

	AttributeSet attr = getAttributes();
	if (attr != null) {
	    setParagraphInsets(attr);
	    Integer a = (Integer)attr.getAttribute(StyleConstants.Alignment);
	    int alignment;
	    if (a == null) {
		Document doc = getElement().getDocument();
		Object o = doc.getProperty(TextAttribute.RUN_DIRECTION);
		if ((o != null) && o.equals(TextAttribute.RUN_DIRECTION_RTL)) {
		    alignment = StyleConstants.ALIGN_RIGHT;
		} else {
		    alignment = StyleConstants.ALIGN_LEFT;
		}
	    } else {
		alignment = a.intValue();
	    }
	    setJustification(alignment);
	    setLineSpacing(StyleConstants.getLineSpacing(attr));
	    setFirstLineIndent(StyleConstants.getFirstLineIndent(attr));
	}