/* * Copyright (c) 1997, 2006, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code 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 * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */package javax.swing.text; import java.util.Arrays;import java.awt.*;import java.awt.font.TextAttribute;import javax.swing.event.*;import javax.swing.SizeRequirements; /** * 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 <code>breakView</code> * method is called to try and carve the view into pieces * that fit. * * @author Timothy Prinzing * @author Scott Violet * @author Igor Kushnirskiy * @see View */public class ParagraphView extends FlowView implements TabExpander { /** * Constructs a <code>ParagraphView</code> for the given element. * * @param elem the element that this view is responsible for */ public ParagraphView(Element elem) { 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()); } } } /** * Sets the type of justification. * * @param j one of the following values: * <ul> * <li><code>StyleConstants.ALIGN_LEFT</code> * <li><code>StyleConstants.ALIGN_CENTER</code> * <li><code>StyleConstants.ALIGN_RIGHT</code> * </ul> */ protected void setJustification(int j) { justification = j; } /** * Sets the line spacing. * * @param ls the value is a factor of the line hight */ protected void setLineSpacing(float ls) { lineSpacing = ls; } /** * Sets the indent on the first line. * * @param fi the value in points */ protected void setFirstLineIndent(float fi) { firstLineIndent = (int) fi; } /** * Set the cached properties from the attributes. */ protected void setPropertiesFromAttributes() { 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)); } } /** * 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 <code>View</code>s * 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 <code>ParagraphView</code> * is responsible for */ protected int getLayoutViewCount() { return layoutPool.getViewCount(); } /** * Returns the view at a given <code>index</code>. * The child views of the paragraph are rows which * have been used to arrange pieces of the <code>Views</code> * 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 <code>index</code> of the desired view * @return the view at <code>index</code> */ protected View getLayoutView(int index) { return layoutPool.getView(index); } /** * Returns the next visual position for the cursor, in * either the east or west direction. * Overridden from <code>CompositeView</code>. * @param pos position into the model * @param b either <code>Position.Bias.Forward</code> or * <code>Position.Bias.Backward</code> * @param a the allocated region to render into * @param direction either <code>SwingConstants.NORTH</code> * or <code>SwingConstants.SOUTH</code> * @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 */ protected int getNextNorthSouthVisualPositionFrom(int pos, Position.Bias b, Shape a, int direction, Position.Bias[] biasRet) throws BadLocationException { 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); } /** * Returns the closest model position to <code>x</code>. * <code>rowIndex</code> 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: * <ul> * <li><code>SwingConstants.NORTH</code> * <li><code>SwingConstants.SOUTH</code> * </ul> * @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 <code>x</code> */ // NOTE: This will not properly work if ParagraphView contains // other ParagraphViews. It won't raise, but this does not message // the children views with getNextVisualPositionFrom. protected int getClosestPositionTo(int pos, Position.Bias b, Shape a, int direction, Position.Bias[] biasRet, int rowIndex, int x) throws BadLocationException { 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; } /** * Determines in which direction the next view lays. * Consider the <code>View</code> at index n. * Typically the <code>View</code>s are layed out * from left to right, so that the <code>View</code> * to the EAST will be at index n + 1, and the * <code>View</code> to the WEST will be at index n - 1. * In certain situations, such as with bidirectional text, * it is possible that the <code>View</code> to EAST is not * at index n + 1, but rather at index n - 1, * or that the <code>View</code> to the WEST is not at * index n - 1, but index n + 1. In this case this method * would return true, indicating the <code>View</code>s are * layed out in descending order. * <p> * 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 <code>Position.Bias.Forward</code> or * <code>Position.Bias.Backward</code> * @return true if the text is layed out right to left at * position, otherwise false. */ protected boolean flipEastAndWestAtEnds(int position, Position.Bias bias) { Document doc = getDocument(); if(doc instanceof AbstractDocument && !((AbstractDocument)doc).isLeftToRight(getStartOffset(), getStartOffset() + 1)) { return true; } return false; } // --- FlowView methods --------------------------------------------- /** * 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 * <code>index</code> * @since 1.3 */ public int getFlowSpan(int index) { View child = getView(index); int adjust = 0; if (child instanceof Row) { Row row = (Row) child; adjust = row.getLeftInset() + row.getRightInset(); } return (layoutSpan == Integer.MAX_VALUE) ? layoutSpan : (layoutSpan - adjust); } /** * 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 * <code>index</code> * @since 1.3 */ public int getFlowStart(int index) { View child = getView(index); int adjust = 0; if (child instanceof Row) { Row row = (Row) child; adjust = row.getLeftInset(); } return tabBase + adjust; } /** * Create a <code>View</code> that should be used to hold a * a row's worth of children in a flow. * @return the new <code>View</code> * @since 1.3 */ protected View createRow() { return new Row(getElement()); } // --- TabExpander methods ------------------------------------------ /** * Returns the next tab stop position given a reference position. * This view implements the tab coordinate system, and calls * <code>getTabbedSpan</code> 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. * <code>LabelView</code> is an example of a view that delegates * its tab expansion needs upward to the paragraph. * <p> * This is implemented to try and locate a <code>TabSet</code> * 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 */ public float nextTabStop(float x, int tabOffset) { // 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; } /** * Gets the <code>Tabset</code> to be used in calculating tabs. * * @return the <code>TabSet</code> */ protected TabSet getTabSet() { return StyleConstants.getTabSet(getElement().getAttributes()); } /** * Returns the size used by the views between * <code>startOffset</code> and <code>endOffset</code>. * This uses <code>getPartialView</code> to calculate the * size if the child view implements the * <code>TabableView</code> interface. If a * size is needed and a <code>View</code> does not implement * the <code>TabableView</code> interface, * the <code>preferredSpan</code> will be used. * * @param startOffset the starting document offset >= 0 * @param endOffset the ending document offset >= startOffsetLines 1–500 of 1201. Load more
/* * Copyright (c) 1997, 2006, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code 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 * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javax.swing.text; import java.util.Arrays; import java.awt.*; import java.awt.font.TextAttribute; import javax.swing.event.*; import javax.swing.SizeRequirements; /** * 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 <code>breakView</code> * method is called to try and carve the view into pieces * that fit. * * @author Timothy Prinzing * @author Scott Violet * @author Igor Kushnirskiy * @see View */ public class ParagraphView extends FlowView implements TabExpander { /** * Constructs a <code>ParagraphView</code> for the given element. * * @param elem the element that this view is responsible for */ public ParagraphView(Element elem) { 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()); } } } /** * Sets the type of justification. * * @param j one of the following values: * <ul> * <li><code>StyleConstants.ALIGN_LEFT</code> * <li><code>StyleConstants.ALIGN_CENTER</code> * <li><code>StyleConstants.ALIGN_RIGHT</code> * </ul> */ protected void setJustification(int j) { justification = j; } /** * Sets the line spacing. * * @param ls the value is a factor of the line hight */ protected void setLineSpacing(float ls) { lineSpacing = ls; } /** * Sets the indent on the first line. * * @param fi the value in points */ protected void setFirstLineIndent(float fi) { firstLineIndent = (int) fi; } /** * Set the cached properties from the attributes. */ protected void setPropertiesFromAttributes() { 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)); } } /** * 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 <code>View</code>s * 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 <code>ParagraphView</code> * is responsible for */ protected int getLayoutViewCount() { return layoutPool.getViewCount(); } /** * Returns the view at a given <code>index</code>. * The child views of the paragraph are rows which * have been used to arrange pieces of the <code>Views</code> * 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 <code>index</code> of the desired view * @return the view at <code>index</code> */ protected View getLayoutView(int index) { return layoutPool.getView(index); } /** * Returns the next visual position for the cursor, in * either the east or west direction. * Overridden from <code>CompositeView</code>. * @param pos position into the model * @param b either <code>Position.Bias.Forward</code> or * <code>Position.Bias.Backward</code> * @param a the allocated region to render into * @param direction either <code>SwingConstants.NORTH</code> * or <code>SwingConstants.SOUTH</code> * @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 */ protected int getNextNorthSouthVisualPositionFrom(int pos, Position.Bias b, Shape a, int direction, Position.Bias[] biasRet) throws BadLocationException { 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); } /** * Returns the closest model position to <code>x</code>. * <code>rowIndex</code> 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: * <ul> * <li><code>SwingConstants.NORTH</code> * <li><code>SwingConstants.SOUTH</code> * </ul> * @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 <code>x</code> */ // NOTE: This will not properly work if ParagraphView contains // other ParagraphViews. It won't raise, but this does not message // the children views with getNextVisualPositionFrom. protected int getClosestPositionTo(int pos, Position.Bias b, Shape a, int direction, Position.Bias[] biasRet, int rowIndex, int x) throws BadLocationException { 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; } /** * Determines in which direction the next view lays. * Consider the <code>View</code> at index n. * Typically the <code>View</code>s are layed out * from left to right, so that the <code>View</code> * to the EAST will be at index n + 1, and the * <code>View</code> to the WEST will be at index n - 1. * In certain situations, such as with bidirectional text, * it is possible that the <code>View</code> to EAST is not * at index n + 1, but rather at index n - 1, * or that the <code>View</code> to the WEST is not at * index n - 1, but index n + 1. In this case this method * would return true, indicating the <code>View</code>s are * layed out in descending order. * <p> * 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 <code>Position.Bias.Forward</code> or * <code>Position.Bias.Backward</code> * @return true if the text is layed out right to left at * position, otherwise false. */ protected boolean flipEastAndWestAtEnds(int position, Position.Bias bias) { Document doc = getDocument(); if(doc instanceof AbstractDocument && !((AbstractDocument)doc).isLeftToRight(getStartOffset(), getStartOffset() + 1)) { return true; } return false; } // --- FlowView methods --------------------------------------------- /** * 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 * <code>index</code> * @since 1.3 */ public int getFlowSpan(int index) { View child = getView(index); int adjust = 0; if (child instanceof Row) { Row row = (Row) child; adjust = row.getLeftInset() + row.getRightInset(); } return (layoutSpan == Integer.MAX_VALUE) ? layoutSpan : (layoutSpan - adjust); } /** * 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 * <code>index</code> * @since 1.3 */ public int getFlowStart(int index) { View child = getView(index); int adjust = 0; if (child instanceof Row) { Row row = (Row) child; adjust = row.getLeftInset(); } return tabBase + adjust; } /** * Create a <code>View</code> that should be used to hold a * a row's worth of children in a flow. * @return the new <code>View</code> * @since 1.3 */ protected View createRow() { return new Row(getElement()); } // --- TabExpander methods ------------------------------------------ /** * Returns the next tab stop position given a reference position. * This view implements the tab coordinate system, and calls * <code>getTabbedSpan</code> 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. * <code>LabelView</code> is an example of a view that delegates * its tab expansion needs upward to the paragraph. * <p> * This is implemented to try and locate a <code>TabSet</code> * 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 */ public float nextTabStop(float x, int tabOffset) { // 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; } /** * Gets the <code>Tabset</code> to be used in calculating tabs. * * @return the <code>TabSet</code> */ protected TabSet getTabSet() { return StyleConstants.getTabSet(getElement().getAttributes()); } /** * Returns the size used by the views between * <code>startOffset</code> and <code>endOffset</code>. * This uses <code>getPartialView</code> to calculate the * size if the child view implements the * <code>TabableView</code> interface. If a * size is needed and a <code>View</code> does not implement * the <code>TabableView</code> interface, * the <code>preferredSpan</code> will be used. * * @param startOffset the starting document offset >= 0 * @param endOffset the ending document offset >= startOffset