0001 /*
0002 * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
0003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004 *
0005 * This code is free software; you can redistribute it and/or modify it
0006 * under the terms of the GNU General Public License version 2 only, as
0007 * published by the Free Software Foundation. Sun designates this
0008 * particular file as subject to the "Classpath" exception as provided
0009 * by Sun in the LICENSE file that accompanied this code.
0010 *
0011 * This code is distributed in the hope that it will be useful, but WITHOUT
0012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014 * version 2 for more details (a copy is included in the LICENSE file that
0015 * accompanied this code).
0016 *
0017 * You should have received a copy of the GNU General Public License version
0018 * 2 along with this work; if not, write to the Free Software Foundation,
0019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020 *
0021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022 * CA 95054 USA or visit www.sun.com if you need additional information or
0023 * have any questions.
0024 */
0025 package javax.swing.text;
0026
0027 import java.util.Arrays;
0028 import java.awt.*;
0029 import java.awt.font.TextAttribute;
0030 import javax.swing.event.*;
0031 import javax.swing.SizeRequirements;
0032
0033 /**
0034 * View of a simple line-wrapping paragraph that supports
0035 * multiple fonts, colors, components, icons, etc. It is
0036 * basically a vertical box with a margin around it. The
0037 * contents of the box are a bunch of rows which are special
0038 * horizontal boxes. This view creates a collection of
0039 * views that represent the child elements of the paragraph
0040 * element. Each of these views are placed into a row
0041 * directly if they will fit, otherwise the <code>breakView</code>
0042 * method is called to try and carve the view into pieces
0043 * that fit.
0044 *
0045 * @author Timothy Prinzing
0046 * @author Scott Violet
0047 * @author Igor Kushnirskiy
0048 * @version 1.103 05/05/07
0049 * @see View
0050 */
0051 public class ParagraphView extends FlowView implements TabExpander {
0052
0053 /**
0054 * Constructs a <code>ParagraphView</code> for the given element.
0055 *
0056 * @param elem the element that this view is responsible for
0057 */
0058 public ParagraphView(Element elem) {
0059 super (elem, View.Y_AXIS);
0060 setPropertiesFromAttributes();
0061 Document doc = elem.getDocument();
0062 Object i18nFlag = doc
0063 .getProperty(AbstractDocument.I18NProperty);
0064 if ((i18nFlag != null) && i18nFlag.equals(Boolean.TRUE)) {
0065 try {
0066 if (i18nStrategy == null) {
0067 // the classname should probably come from a property file.
0068 String classname = "javax.swing.text.TextLayoutStrategy";
0069 ClassLoader loader = getClass().getClassLoader();
0070 if (loader != null) {
0071 i18nStrategy = loader.loadClass(classname);
0072 } else {
0073 i18nStrategy = Class.forName(classname);
0074 }
0075 }
0076 Object o = i18nStrategy.newInstance();
0077 if (o instanceof FlowStrategy) {
0078 strategy = (FlowStrategy) o;
0079 }
0080 } catch (Throwable e) {
0081 throw new StateInvariantError(
0082 "ParagraphView: Can't create i18n strategy: "
0083 + e.getMessage());
0084 }
0085 }
0086 }
0087
0088 /**
0089 * Sets the type of justification.
0090 *
0091 * @param j one of the following values:
0092 * <ul>
0093 * <li><code>StyleConstants.ALIGN_LEFT</code>
0094 * <li><code>StyleConstants.ALIGN_CENTER</code>
0095 * <li><code>StyleConstants.ALIGN_RIGHT</code>
0096 * </ul>
0097 */
0098 protected void setJustification(int j) {
0099 justification = j;
0100 }
0101
0102 /**
0103 * Sets the line spacing.
0104 *
0105 * @param ls the value is a factor of the line hight
0106 */
0107 protected void setLineSpacing(float ls) {
0108 lineSpacing = ls;
0109 }
0110
0111 /**
0112 * Sets the indent on the first line.
0113 *
0114 * @param fi the value in points
0115 */
0116 protected void setFirstLineIndent(float fi) {
0117 firstLineIndent = (int) fi;
0118 }
0119
0120 /**
0121 * Set the cached properties from the attributes.
0122 */
0123 protected void setPropertiesFromAttributes() {
0124 AttributeSet attr = getAttributes();
0125 if (attr != null) {
0126 setParagraphInsets(attr);
0127 Integer a = (Integer) attr
0128 .getAttribute(StyleConstants.Alignment);
0129 int alignment;
0130 if (a == null) {
0131 Document doc = getElement().getDocument();
0132 Object o = doc.getProperty(TextAttribute.RUN_DIRECTION);
0133 if ((o != null)
0134 && o.equals(TextAttribute.RUN_DIRECTION_RTL)) {
0135 alignment = StyleConstants.ALIGN_RIGHT;
0136 } else {
0137 alignment = StyleConstants.ALIGN_LEFT;
0138 }
0139 } else {
0140 alignment = a.intValue();
0141 }
0142 setJustification(alignment);
0143 setLineSpacing(StyleConstants.getLineSpacing(attr));
0144 setFirstLineIndent(StyleConstants.getFirstLineIndent(attr));
0145 }
0146 }
0147
0148 /**
0149 * Returns the number of views that this view is
0150 * responsible for.
0151 * The child views of the paragraph are rows which
0152 * have been used to arrange pieces of the <code>View</code>s
0153 * that represent the child elements. This is the number
0154 * of views that have been tiled in two dimensions,
0155 * and should be equivalent to the number of child elements
0156 * to the element this view is responsible for.
0157 *
0158 * @return the number of views that this <code>ParagraphView</code>
0159 * is responsible for
0160 */
0161 protected int getLayoutViewCount() {
0162 return layoutPool.getViewCount();
0163 }
0164
0165 /**
0166 * Returns the view at a given <code>index</code>.
0167 * The child views of the paragraph are rows which
0168 * have been used to arrange pieces of the <code>Views</code>
0169 * that represent the child elements. This methods returns
0170 * the view responsible for the child element index
0171 * (prior to breaking). These are the Views that were
0172 * produced from a factory (to represent the child
0173 * elements) and used for layout.
0174 *
0175 * @param index the <code>index</code> of the desired view
0176 * @return the view at <code>index</code>
0177 */
0178 protected View getLayoutView(int index) {
0179 return layoutPool.getView(index);
0180 }
0181
0182 /**
0183 * Adjusts the given row if possible to fit within the
0184 * layout span. By default this will try to find the
0185 * highest break weight possible nearest the end of
0186 * the row. If a forced break is encountered, the
0187 * break will be positioned there.
0188 * <p>
0189 * This is meant for internal usage, and should not be used directly.
0190 *
0191 * @param r the row to adjust to the current layout
0192 * span
0193 * @param desiredSpan the current layout span >= 0
0194 * @param x the location r starts at
0195 */
0196 protected void adjustRow(Row r, int desiredSpan, int x) {
0197 }
0198
0199 /**
0200 * Returns the next visual position for the cursor, in
0201 * either the east or west direction.
0202 * Overridden from <code>CompositeView</code>.
0203 * @param pos position into the model
0204 * @param b either <code>Position.Bias.Forward</code> or
0205 * <code>Position.Bias.Backward</code>
0206 * @param a the allocated region to render into
0207 * @param direction either <code>SwingConstants.NORTH</code>
0208 * or <code>SwingConstants.SOUTH</code>
0209 * @param biasRet an array containing the bias that were checked
0210 * in this method
0211 * @return the location in the model that represents the
0212 * next location visual position
0213 */
0214 protected int getNextNorthSouthVisualPositionFrom(int pos,
0215 Position.Bias b, Shape a, int direction,
0216 Position.Bias[] biasRet) throws BadLocationException {
0217 int vIndex;
0218 if (pos == -1) {
0219 vIndex = (direction == NORTH) ? getViewCount() - 1 : 0;
0220 } else {
0221 if (b == Position.Bias.Backward && pos > 0) {
0222 vIndex = getViewIndexAtPosition(pos - 1);
0223 } else {
0224 vIndex = getViewIndexAtPosition(pos);
0225 }
0226 if (direction == NORTH) {
0227 if (vIndex == 0) {
0228 return -1;
0229 }
0230 vIndex--;
0231 } else if (++vIndex >= getViewCount()) {
0232 return -1;
0233 }
0234 }
0235 // vIndex gives index of row to look in.
0236 JTextComponent text = (JTextComponent) getContainer();
0237 Caret c = text.getCaret();
0238 Point magicPoint;
0239 magicPoint = (c != null) ? c.getMagicCaretPosition() : null;
0240 int x;
0241 if (magicPoint == null) {
0242 Shape posBounds;
0243 try {
0244 posBounds = text.getUI().modelToView(text, pos, b);
0245 } catch (BadLocationException exc) {
0246 posBounds = null;
0247 }
0248 if (posBounds == null) {
0249 x = 0;
0250 } else {
0251 x = posBounds.getBounds().x;
0252 }
0253 } else {
0254 x = magicPoint.x;
0255 }
0256 return getClosestPositionTo(pos, b, a, direction, biasRet,
0257 vIndex, x);
0258 }
0259
0260 /**
0261 * Returns the closest model position to <code>x</code>.
0262 * <code>rowIndex</code> gives the index of the view that corresponds
0263 * that should be looked in.
0264 * @param pos position into the model
0265 * @param a the allocated region to render into
0266 * @param direction one of the following values:
0267 * <ul>
0268 * <li><code>SwingConstants.NORTH</code>
0269 * <li><code>SwingConstants.SOUTH</code>
0270 * </ul>
0271 * @param biasRet an array containing the bias that were checked
0272 * in this method
0273 * @param rowIndex the index of the view
0274 * @param x the x coordinate of interest
0275 * @return the closest model position to <code>x</code>
0276 */
0277 // NOTE: This will not properly work if ParagraphView contains
0278 // other ParagraphViews. It won't raise, but this does not message
0279 // the children views with getNextVisualPositionFrom.
0280 protected int getClosestPositionTo(int pos, Position.Bias b,
0281 Shape a, int direction, Position.Bias[] biasRet,
0282 int rowIndex, int x) throws BadLocationException {
0283 JTextComponent text = (JTextComponent) getContainer();
0284 Document doc = getDocument();
0285 AbstractDocument aDoc = (doc instanceof AbstractDocument) ? (AbstractDocument) doc
0286 : null;
0287 View row = getView(rowIndex);
0288 int lastPos = -1;
0289 // This could be made better to check backward positions too.
0290 biasRet[0] = Position.Bias.Forward;
0291 for (int vc = 0, numViews = row.getViewCount(); vc < numViews; vc++) {
0292 View v = row.getView(vc);
0293 int start = v.getStartOffset();
0294 boolean ltr = (aDoc != null) ? aDoc.isLeftToRight(start,
0295 start + 1) : true;
0296 if (ltr) {
0297 lastPos = start;
0298 for (int end = v.getEndOffset(); lastPos < end; lastPos++) {
0299 float xx = text.modelToView(lastPos).getBounds().x;
0300 if (xx >= x) {
0301 while (++lastPos < end
0302 && text.modelToView(lastPos)
0303 .getBounds().x == xx) {
0304 }
0305 return --lastPos;
0306 }
0307 }
0308 lastPos--;
0309 } else {
0310 for (lastPos = v.getEndOffset() - 1; lastPos >= start; lastPos--) {
0311 float xx = text.modelToView(lastPos).getBounds().x;
0312 if (xx >= x) {
0313 while (--lastPos >= start
0314 && text.modelToView(lastPos)
0315 .getBounds().x == xx) {
0316 }
0317 return ++lastPos;
0318 }
0319 }
0320 lastPos++;
0321 }
0322 }
0323 if (lastPos == -1) {
0324 return getStartOffset();
0325 }
0326 return lastPos;
0327 }
0328
0329 /**
0330 * Determines in which direction the next view lays.
0331 * Consider the <code>View</code> at index n.
0332 * Typically the <code>View</code>s are layed out
0333 * from left to right, so that the <code>View</code>
0334 * to the EAST will be at index n + 1, and the
0335 * <code>View</code> to the WEST will be at index n - 1.
0336 * In certain situations, such as with bidirectional text,
0337 * it is possible that the <code>View</code> to EAST is not
0338 * at index n + 1, but rather at index n - 1,
0339 * or that the <code>View</code> to the WEST is not at
0340 * index n - 1, but index n + 1. In this case this method
0341 * would return true, indicating the <code>View</code>s are
0342 * layed out in descending order.
0343 * <p>
0344 * This will return true if the text is layed out right
0345 * to left at position, otherwise false.
0346 *
0347 * @param position position into the model
0348 * @param bias either <code>Position.Bias.Forward</code> or
0349 * <code>Position.Bias.Backward</code>
0350 * @return true if the text is layed out right to left at
0351 * position, otherwise false.
0352 */
0353 protected boolean flipEastAndWestAtEnds(int position,
0354 Position.Bias bias) {
0355 Document doc = getDocument();
0356 if (doc instanceof AbstractDocument
0357 && !((AbstractDocument) doc).isLeftToRight(
0358 getStartOffset(), getStartOffset() + 1)) {
0359 return true;
0360 }
0361 return false;
0362 }
0363
0364 // --- FlowView methods ---------------------------------------------
0365
0366 /**
0367 * Fetches the constraining span to flow against for
0368 * the given child index.
0369 * @param index the index of the view being queried
0370 * @return the constraining span for the given view at
0371 * <code>index</code>
0372 * @since 1.3
0373 */
0374 public int getFlowSpan(int index) {
0375 View child = getView(index);
0376 int adjust = 0;
0377 if (child instanceof Row) {
0378 Row row = (Row) child;
0379 adjust = row.getLeftInset() + row.getRightInset();
0380 }
0381 return (layoutSpan == Integer.MAX_VALUE) ? layoutSpan
0382 : (layoutSpan - adjust);
0383 }
0384
0385 /**
0386 * Fetches the location along the flow axis that the
0387 * flow span will start at.
0388 * @param index the index of the view being queried
0389 * @return the location for the given view at
0390 * <code>index</code>
0391 * @since 1.3
0392 */
0393 public int getFlowStart(int index) {
0394 View child = getView(index);
0395 int adjust = 0;
0396 if (child instanceof Row) {
0397 Row row = (Row) child;
0398 adjust = row.getLeftInset();
0399 }
0400 return tabBase + adjust;
0401 }
0402
0403 /**
0404 * Create a <code>View</code> that should be used to hold a
0405 * a row's worth of children in a flow.
0406 * @return the new <code>View</code>
0407 * @since 1.3
0408 */
0409 protected View createRow() {
0410 return new Row(getElement());
0411 }
0412
0413 // --- TabExpander methods ------------------------------------------
0414
0415 /**
0416 * Returns the next tab stop position given a reference position.
0417 * This view implements the tab coordinate system, and calls
0418 * <code>getTabbedSpan</code> on the logical children in the process
0419 * of layout to determine the desired span of the children. The
0420 * logical children can delegate their tab expansion upward to
0421 * the paragraph which knows how to expand tabs.
0422 * <code>LabelView</code> is an example of a view that delegates
0423 * its tab expansion needs upward to the paragraph.
0424 * <p>
0425 * This is implemented to try and locate a <code>TabSet</code>
0426 * in the paragraph element's attribute set. If one can be
0427 * found, its settings will be used, otherwise a default expansion
0428 * will be provided. The base location for for tab expansion
0429 * is the left inset from the paragraphs most recent allocation
0430 * (which is what the layout of the children is based upon).
0431 *
0432 * @param x the X reference position
0433 * @param tabOffset the position within the text stream
0434 * that the tab occurred at >= 0
0435 * @return the trailing end of the tab expansion >= 0
0436 * @see TabSet
0437 * @see TabStop
0438 * @see LabelView
0439 */
0440 public float nextTabStop(float x, int tabOffset) {
0441 // If the text isn't left justified, offset by 10 pixels!
0442 if (justification != StyleConstants.ALIGN_LEFT)
0443 return x + 10.0f;
0444 x -= tabBase;
0445 TabSet tabs = getTabSet();
0446 if (tabs == null) {
0447 // a tab every 72 pixels.
0448 return (float) (tabBase + (((int) x / 72 + 1) * 72));
0449 }
0450 TabStop tab = tabs.getTabAfter(x + .01f);
0451 if (tab == null) {
0452 // no tab, do a default of 5 pixels.
0453 // Should this cause a wrapping of the line?
0454 return tabBase + x + 5.0f;
0455 }
0456 int alignment = tab.getAlignment();
0457 int offset;
0458 switch (alignment) {
0459 default:
0460 case TabStop.ALIGN_LEFT:
0461 // Simple case, left tab.
0462 return tabBase + tab.getPosition();
0463 case TabStop.ALIGN_BAR:
0464 // PENDING: what does this mean?
0465 return tabBase + tab.getPosition();
0466 case TabStop.ALIGN_RIGHT:
0467 case TabStop.ALIGN_CENTER:
0468 offset = findOffsetToCharactersInString(tabChars,
0469 tabOffset + 1);
0470 break;
0471 case TabStop.ALIGN_DECIMAL:
0472 offset = findOffsetToCharactersInString(tabDecimalChars,
0473 tabOffset + 1);
0474 break;
0475 }
0476 if (offset == -1) {
0477 offset = getEndOffset();
0478 }
0479 float charsSize = getPartialSize(tabOffset + 1, offset);
0480 switch (alignment) {
0481 case TabStop.ALIGN_RIGHT:
0482 case TabStop.ALIGN_DECIMAL:
0483 // right and decimal are treated the same way, the new
0484 // position will be the location of the tab less the
0485 // partialSize.
0486 return tabBase + Math.max(x, tab.getPosition() - charsSize);
0487 case TabStop.ALIGN_CENTER:
0488 // Similar to right, but half the partialSize.
0489 return tabBase
0490 + Math.max(x, tab.getPosition() - charsSize / 2.0f);
0491 }
0492 // will never get here!
0493 return x;
0494 }
0495
0496 /**
0497 * Gets the <code>Tabset</code> to be used in calculating tabs.
0498 *
0499 * @return the <code>TabSet</code>
0500 */
0501 protected TabSet getTabSet() {
0502 return StyleConstants.getTabSet(getElement().getAttributes());
0503 }
0504
0505 /**
0506 * Returns the size used by the views between
0507 * <code>startOffset</code> and <code>endOffset</code>.
0508 * This uses <code>getPartialView</code> to calculate the
0509 * size if the child view implements the
0510 * <code>TabableView</code> interface. If a
0511 * size is needed and a <code>View</code> does not implement
0512 * the <code>TabableView</code> interface,
0513 * the <code>preferredSpan</code> will be used.
0514 *
0515 * @param startOffset the starting document offset >= 0
0516 * @param endOffset the ending document offset >= startOffset
0517 * @return the size >= 0
0518 */
0519 protected float getPartialSize(int startOffset, int endOffset) {
0520 float size = 0.0f;
0521 int viewIndex;
0522 int numViews = getViewCount();
0523 View view;
0524 int viewEnd;
0525 int tempEnd;
0526
0527 // Have to search layoutPool!
0528 // PENDING: when ParagraphView supports breaking location
0529 // into layoutPool will have to change!
0530 viewIndex = getElement().getElementIndex(startOffset);
0531 numViews = layoutPool.getViewCount();
0532 while (startOffset < endOffset && viewIndex < numViews) {
0533 view = layoutPool.getView(viewIndex++);
0534 viewEnd = view.getEndOffset();
0535 tempEnd = Math.min(endOffset, viewEnd);
0536 if (view instanceof TabableView)
0537 size += ((TabableView) view).getPartialSpan(
0538 startOffset, tempEnd);
0539 else if (startOffset == view.getStartOffset()
0540 && tempEnd == view.getEndOffset())
0541 size += view.getPreferredSpan(View.X_AXIS);
0542 else
0543 // PENDING: should we handle this better?
0544 return 0.0f;
0545 startOffset = viewEnd;
0546 }
0547 return size;
0548 }
0549
0550 /**
0551 * Finds the next character in the document with a character in
0552 * <code>string</code>, starting at offset <code>start</code>. If
0553 * there are no characters found, -1 will be returned.
0554 *
0555 * @param string the string of characters
0556 * @param start where to start in the model >= 0
0557 * @return the document offset, or -1 if no characters found
0558 */
0559 protected int findOffsetToCharactersInString(char[] string,
0560 int start) {
0561 int stringLength = string.length;
0562 int end = getEndOffset();
0563 Segment seg = new Segment();
0564 try {
0565 getDocument().getText(start, end - start, seg);
0566 } catch (BadLocationException ble) {
0567 return -1;
0568 }
0569 for (int counter = seg.offset, maxCounter = seg.offset
0570 + seg.count; counter < maxCounter; counter++) {
0571 char currentChar = seg.array[counter];
0572 for (int subCounter = 0; subCounter < stringLength; subCounter++) {
0573 if (currentChar == string[subCounter])
0574 return counter - seg.offset + start;
0575 }
0576 }
0577 // No match.
0578 return -1;
0579 }
0580
0581 /**
0582 * Returns where the tabs are calculated from.
0583 * @return where tabs are calculated from
0584 */
0585 protected float getTabBase() {
0586 return (float) tabBase;
0587 }
0588
0589 // ---- View methods ----------------------------------------------------
0590
0591 /**
0592 * Renders using the given rendering surface and area on that
0593 * surface. This is implemented to delgate to the superclass
0594 * after stashing the base coordinate for tab calculations.
0595 *
0596 * @param g the rendering surface to use
0597 * @param a the allocated region to render into
0598 * @see View#paint
0599 */
0600 public void paint(Graphics g, Shape a) {
0601 Rectangle alloc = (a instanceof Rectangle) ? (Rectangle) a : a
0602 .getBounds();
0603 tabBase = alloc.x + getLeftInset();
0604 super .paint(g, a);
0605
0606 // line with the negative firstLineIndent value needs
0607 // special handling
0608 if (firstLineIndent < 0) {
0609 Shape sh = getChildAllocation(0, a);
0610 if ((sh != null) && sh.intersects(alloc)) {
0611 int x = alloc.x + getLeftInset() + firstLineIndent;
0612 int y = alloc.y + getTopInset();
0613
0614 Rectangle clip = g.getClipBounds();
0615 tempRect.x = x + getOffset(X_AXIS, 0);
0616 tempRect.y = y + getOffset(Y_AXIS, 0);
0617 tempRect.width = getSpan(X_AXIS, 0) - firstLineIndent;
0618 tempRect.height = getSpan(Y_AXIS, 0);
0619 if (tempRect.intersects(clip)) {
0620 tempRect.x = tempRect.x - firstLineIndent;
0621 paintChild(g, tempRect, 0);
0622 }
0623 }
0624 }
0625 }
0626
0627 /**
0628 * Determines the desired alignment for this view along an
0629 * axis. This is implemented to give the alignment to the
0630 * center of the first row along the y axis, and the default
0631 * along the x axis.
0632 *
0633 * @param axis may be either <code>View.X_AXIS</code> or
0634 * <code>View.Y_AXIS</code>
0635 * @return the desired alignment. This should be a value
0636 * between 0.0 and 1.0 inclusive, where 0 indicates alignment at the
0637 * origin and 1.0 indicates alignment to the full span
0638 * away from the origin. An alignment of 0.5 would be the
0639 * center of the view.
0640 */
0641 public float getAlignment(int axis) {
0642 switch (axis) {
0643 case Y_AXIS:
0644 float a = 0.5f;
0645 if (getViewCount() != 0) {
0646 int paragraphSpan = (int) getPreferredSpan(View.Y_AXIS);
0647 View v = getView(0);
0648 int rowSpan = (int) v.getPreferredSpan(View.Y_AXIS);
0649 a = (paragraphSpan != 0) ? ((float) (rowSpan / 2))
0650 / paragraphSpan : 0;
0651 }
0652 return a;
0653 case X_AXIS:
0654 return 0.5f;
0655 default:
0656 throw new IllegalArgumentException("Invalid axis: " + axis);
0657 }
0658 }
0659
0660 /**
0661 * Breaks this view on the given axis at the given length.
0662 * <p>
0663 * <code>ParagraphView</code> instances are breakable
0664 * along the <code>Y_AXIS</code> only, and only if
0665 * <code>len</code> is after the first line.
0666 *
0667 * @param axis may be either <code>View.X_AXIS</code>
0668 * or <code>View.Y_AXIS</code>
0669 * @param len specifies where a potential break is desired
0670 * along the given axis >= 0
0671 * @param a the current allocation of the view
0672 * @return the fragment of the view that represents the
0673 * given span, if the view can be broken; if the view
0674 * doesn't support breaking behavior, the view itself is
0675 * returned
0676 * @see View#breakView
0677 */
0678 public View breakView(int axis, float len, Shape a) {
0679 if (axis == View.Y_AXIS) {
0680 if (a != null) {
0681 Rectangle alloc = a.getBounds();
0682 setSize(alloc.width, alloc.height);
0683 }
0684 // Determine what row to break on.
0685
0686 // PENDING(prinz) add break support
0687 return this ;
0688 }
0689 return this ;
0690 }
0691
0692 /**
0693 * Gets the break weight for a given location.
0694 * <p>
0695 * <code>ParagraphView</code> instances are breakable
0696 * along the <code>Y_AXIS</code> only, and only if
0697 * <code>len</code> is after the first row. If the length
0698 * is less than one row, a value of <code>BadBreakWeight</code>
0699 * is returned.
0700 *
0701 * @param axis may be either <code>View.X_AXIS</code>
0702 * or <code>View.Y_AXIS</code>
0703 * @param len specifies where a potential break is desired >= 0
0704 * @return a value indicating the attractiveness of breaking here;
0705 * either <code>GoodBreakWeight</code> or <code>BadBreakWeight</code>
0706 * @see View#getBreakWeight
0707 */
0708 public int getBreakWeight(int axis, float len) {
0709 if (axis == View.Y_AXIS) {
0710 // PENDING(prinz) make this return a reasonable value
0711 // when paragraph breaking support is re-implemented.
0712 // If less than one row, bad weight value should be
0713 // returned.
0714 //return GoodBreakWeight;
0715 return BadBreakWeight;
0716 }
0717 return BadBreakWeight;
0718 }
0719
0720 /**
0721 * Gives notification from the document that attributes were changed
0722 * in a location that this view is responsible for.
0723 *
0724 * @param changes the change information from the
0725 * associated document
0726 * @param a the current allocation of the view
0727 * @param f the factory to use to rebuild if the view has children
0728 * @see View#changedUpdate
0729 */
0730 public void changedUpdate(DocumentEvent changes, Shape a,
0731 ViewFactory f) {
0732 // update any property settings stored, and layout should be
0733 // recomputed
0734 setPropertiesFromAttributes();
0735 layoutChanged(X_AXIS);
0736 layoutChanged(Y_AXIS);
0737 super .changedUpdate(changes, a, f);
0738 }
0739
0740 // --- variables -----------------------------------------------
0741
0742 private int justification;
0743 private float lineSpacing;
0744 /** Indentation for the first line, from the left inset. */
0745 protected int firstLineIndent = 0;
0746
0747 /**
0748 * Used by the TabExpander functionality to determine
0749 * where to base the tab calculations. This is basically
0750 * the location of the left side of the paragraph.
0751 */
0752 private int tabBase;
0753
0754 /**
0755 * Used to create an i18n-based layout strategy
0756 */
0757 static Class i18nStrategy;
0758
0759 /** Used for searching for a tab. */
0760 static char[] tabChars;
0761 /** Used for searching for a tab or decimal character. */
0762 static char[] tabDecimalChars;
0763
0764 static {
0765 tabChars = new char[1];
0766 tabChars[0] = '\t';
0767 tabDecimalChars = new char[2];
0768 tabDecimalChars[0] = '\t';
0769 tabDecimalChars[1] = '.';
0770 }
0771
0772 /**
0773 * Internally created view that has the purpose of holding
0774 * the views that represent the children of the paragraph
0775 * that have been arranged in rows.
0776 */
0777 class Row extends BoxView {
0778
0779 Row(Element elem) {
0780 super (elem, View.X_AXIS);
0781 }
0782
0783 /**
0784 * This is reimplemented to do nothing since the
0785 * paragraph fills in the row with its needed
0786 * children.
0787 */
0788 protected void loadChildren(ViewFactory f) {
0789 }
0790
0791 /**
0792 * Fetches the attributes to use when rendering. This view
0793 * isn't directly responsible for an element so it returns
0794 * the outer classes attributes.
0795 */
0796 public AttributeSet getAttributes() {
0797 View p = getParent();
0798 return (p != null) ? p.getAttributes() : null;
0799 }
0800
0801 public float getAlignment(int axis) {
0802 if (axis == View.X_AXIS) {
0803 switch (justification) {
0804 case StyleConstants.ALIGN_LEFT:
0805 return 0;
0806 case StyleConstants.ALIGN_RIGHT:
0807 return 1;
0808 case StyleConstants.ALIGN_CENTER:
0809 return 0.5f;
0810 case StyleConstants.ALIGN_JUSTIFIED:
0811 float rv = 0.5f;
0812 //if we can justifiy the content always align to
0813 //the left.
0814 if (isJustifiableDocument()) {
0815 rv = 0f;
0816 }
0817 return rv;
0818 }
0819 }
0820 return super .getAlignment(axis);
0821 }
0822
0823 /**
0824 * Provides a mapping from the document model coordinate space
0825 * to the coordinate space of the view mapped to it. This is
0826 * implemented to let the superclass find the position along
0827 * the major axis and the allocation of the row is used
0828 * along the minor axis, so that even though the children
0829 * are different heights they all get the same caret height.
0830 *
0831 * @param pos the position to convert
0832 * @param a the allocated region to render into
0833 * @return the bounding box of the given position
0834 * @exception BadLocationException if the given position does not represent a
0835 * valid location in the associated document
0836 * @see View#modelToView
0837 */
0838 public Shape modelToView(int pos, Shape a, Position.Bias b)
0839 throws BadLocationException {
0840 Rectangle r = a.getBounds();
0841 View v = getViewAtPosition(pos, r);
0842 if ((v != null) && (!v.getElement().isLeaf())) {
0843 // Don't adjust the height if the view represents a branch.
0844 return super .modelToView(pos, a, b);
0845 }
0846 r = a.getBounds();
0847 int height = r.height;
0848 int y = r.y;
0849 Shape loc = super .modelToView(pos, a, b);
0850 r = loc.getBounds();
0851 r.height = height;
0852 r.y = y;
0853 return r;
0854 }
0855
0856 /**
0857 * Range represented by a row in the paragraph is only
0858 * a subset of the total range of the paragraph element.
0859 * @see View#getRange
0860 */
0861 public int getStartOffset() {
0862 int offs = Integer.MAX_VALUE;
0863 int n = getViewCount();
0864 for (int i = 0; i < n; i++) {
0865 View v = getView(i);
0866 offs = Math.min(offs, v.getStartOffset());
0867 }
0868 return offs;
0869 }
0870
0871 public int getEndOffset() {
0872 int offs = 0;
0873 int n = getViewCount();
0874 for (int i = 0; i < n; i++) {
0875 View v = getView(i);
0876 offs = Math.max(offs, v.getEndOffset());
0877 }
0878 return offs;
0879 }
0880
0881 /**
0882 * Perform layout for the minor axis of the box (i.e. the
0883 * axis orthoginal to the axis that it represents). The results
0884 * of the layout should be placed in the given arrays which represent
0885 * the allocations to the children along the minor axis.
0886 * <p>
0887 * This is implemented to do a baseline layout of the children
0888 * by calling BoxView.baselineLayout.
0889 *
0890 * @param targetSpan the total span given to the view, which
0891 * whould be used to layout the children.
0892 * @param axis the axis being layed out.
0893 * @param offsets the offsets from the origin of the view for
0894 * each of the child views. This is a return value and is
0895 * filled in by the implementation of this method.
0896 * @param spans the span of each child view. This is a return
0897 * value and is filled in by the implementation of this method.
0898 * @return the offset and span for each child view in the
0899 * offsets and spans parameters
0900 */
0901 protected void layoutMinorAxis(int targetSpan, int axis,
0902 int[] offsets, int[] spans) {
0903 baselineLayout(targetSpan, axis, offsets, spans);
0904 }
0905
0906 protected SizeRequirements calculateMinorAxisRequirements(
0907 int axis, SizeRequirements r) {
0908 return baselineRequirements(axis, r);
0909 }
0910
0911 private boolean isLastRow() {
0912 View parent;
0913 return ((parent = getParent()) == null || this == parent
0914 .getView(parent.getViewCount() - 1));
0915 }
0916
0917 private boolean isBrokenRow() {
0918 boolean rv = false;
0919 int viewsCount = getViewCount();
0920 if (viewsCount > 0) {
0921 View lastView = getView(viewsCount - 1);
0922 if (lastView.getBreakWeight(X_AXIS, 0, 0) >= ForcedBreakWeight) {
0923 rv = true;
0924 }
0925 }
0926 return rv;
0927 }
0928
0929 private boolean isJustifiableDocument() {
0930 return (!Boolean.TRUE.equals(getDocument().getProperty(
0931 AbstractDocument.I18NProperty)));
0932 }
0933
0934 /**
0935 * Whether we need to justify this {@code Row}.
0936 * At this time (jdk1.6) we support justification on for non
0937 * 18n text.
0938 *
0939 * @return {@code true} if this {@code Row} should be justified.
0940 */
0941 private boolean isJustifyEnabled() {
0942 boolean ret = (justification == StyleConstants.ALIGN_JUSTIFIED);
0943
0944 //no justification for i18n documents
0945 ret = ret && isJustifiableDocument();
0946
0947 //no justification for the last row
0948 ret = ret && !isLastRow();
0949
0950 //no justification for the broken rows
0951 ret = ret && !isBrokenRow();
0952
0953 return ret;
0954 }
0955
0956 //Calls super method after setting spaceAddon to 0.
0957 //Justification should not affect MajorAxisRequirements
0958 @Override
0959 protected SizeRequirements calculateMajorAxisRequirements(
0960 int axis, SizeRequirements r) {
0961 int oldJustficationData[] = justificationData;
0962 justificationData = null;
0963 SizeRequirements ret = super
0964 .calculateMajorAxisRequirements(axis, r);
0965 if (isJustifyEnabled()) {
0966 justificationData = oldJustficationData;
0967 }
0968 return ret;
0969 }
0970
0971 @Override
0972 protected void layoutMajorAxis(int targetSpan, int axis,
0973 int[] offsets, int[] spans) {
0974 int oldJustficationData[] = justificationData;
0975 justificationData = null;
0976 super .layoutMajorAxis(targetSpan, axis, offsets, spans);
0977 if (!isJustifyEnabled()) {
0978 return;
0979 }
0980
0981 int currentSpan = 0;
0982 for (int span : spans) {
0983 currentSpan += span;
0984 }
0985 if (currentSpan == targetSpan) {
0986 //no need to justify
0987 return;
0988 }
0989
0990 // we justify text by enlarging spaces by the {@code spaceAddon}.
0991 // justification is started to the right of the rightmost TAB.
0992 // leading and trailing spaces are not extendable.
0993 //
0994 // GlyphPainter1 uses
0995 // justificationData
0996 // for all painting and measurement.
0997
0998 int extendableSpaces = 0;
0999 int startJustifiableContent = -1;
1000 int endJustifiableContent = -1;
1001 int lastLeadingSpaces = 0;
1002
1003 int rowStartOffset = getStartOffset();
1004 int rowEndOffset = getEndOffset();
1005 int spaceMap[] = new int[rowEndOffset - rowStartOffset];
1006 Arrays.fill(spaceMap, 0);
1007 for (int i = getViewCount() - 1; i >= 0; i--) {
1008 View view = getView(i);
1009 if (view instanceof GlyphView) {
1010 GlyphView.JustificationInfo justificationInfo = ((GlyphView) view)
1011 .getJustificationInfo(rowStartOffset);
1012 final int viewStartOffset = view.getStartOffset();
1013 final int offset = viewStartOffset - rowStartOffset;
1014 for (int j = 0; j < justificationInfo.spaceMap
1015 .length(); j++) {
1016 if (justificationInfo.spaceMap.get(j)) {
1017 spaceMap[j + offset] = 1;
1018 }
1019 }
1020 if (startJustifiableContent > 0) {
1021 if (justificationInfo.end >= 0) {
1022 extendableSpaces += justificationInfo.trailingSpaces;
1023 } else {
1024 lastLeadingSpaces += justificationInfo.trailingSpaces;
1025 }
1026 }
1027 if (justificationInfo.start >= 0) {
1028 startJustifiableContent = justificationInfo.start
1029 + viewStartOffset;
1030 extendableSpaces += lastLeadingSpaces;
1031 }
1032 if (justificationInfo.end >= 0
1033 && endJustifiableContent < 0) {
1034 endJustifiableContent = justificationInfo.end
1035 + viewStartOffset;
1036 }
1037 extendableSpaces += justificationInfo.contentSpaces;
1038 lastLeadingSpaces = justificationInfo.leadingSpaces;
1039 if (justificationInfo.hasTab) {
1040 break;
1041 }
1042 }
1043 }
1044 if (extendableSpaces <= 0) {
1045 //there is nothing we can do to justify
1046 return;
1047 }
1048 int adjustment = (targetSpan - currentSpan);
1049 int spaceAddon = (extendableSpaces > 0) ? adjustment
1050 / extendableSpaces : 0;
1051 int spaceAddonLeftoverEnd = -1;
1052 for (int i = startJustifiableContent - rowStartOffset, leftover = adjustment
1053 - spaceAddon * extendableSpaces; leftover > 0; leftover -= spaceMap[i], i++) {
1054 spaceAddonLeftoverEnd = i;
1055 }
1056 if (spaceAddon > 0 || spaceAddonLeftoverEnd >= 0) {
1057 justificationData = (oldJustficationData != null) ? oldJustficationData
1058 : new int[END_JUSTIFIABLE + 1];
1059 justificationData[SPACE_ADDON] = spaceAddon;
1060 justificationData[SPACE_ADDON_LEFTOVER_END] = spaceAddonLeftoverEnd;
1061 justificationData[START_JUSTIFIABLE] = startJustifiableContent
1062 - rowStartOffset;
1063 justificationData[END_JUSTIFIABLE] = endJustifiableContent
1064 - rowStartOffset;
1065 super .layoutMajorAxis(targetSpan, axis, offsets, spans);
1066 }
1067 }
1068
1069 //for justified row we assume the maximum horizontal span
1070 //is MAX_VALUE.
1071 @Override
1072 public float getMaximumSpan(int axis) {
1073 float ret;
1074 if (View.X_AXIS == axis && isJustifyEnabled()) {
1075 ret = Float.MAX_VALUE;
1076 } else {
1077 ret = super .getMaximumSpan(axis);
1078 }
1079 return ret;
1080 }
1081
1082 /**
1083 * Fetches the child view index representing the given position in
1084 * the model.
1085 *
1086 * @param pos the position >= 0
1087 * @return index of the view representing the given position, or
1088 * -1 if no view represents that position
1089 */
1090 protected int getViewIndexAtPosition(int pos) {
1091 // This is expensive, but are views are not necessarily layed
1092 // out in model order.
1093 if (pos < getStartOffset() || pos >= getEndOffset())
1094 return -1;
1095 for (int counter = getViewCount() - 1; counter >= 0; counter--) {
1096 View v = getView(counter);
1097 if (pos >= v.getStartOffset() && pos < v.getEndOffset()) {
1098 return counter;
1099 }
1100 }
1101 return -1;
1102 }
1103
1104 /**
1105 * Gets the left inset.
1106 *
1107 * @return the inset
1108 */
1109 protected short getLeftInset() {
1110 View parentView;
1111 int adjustment = 0;
1112 if ((parentView = getParent()) != null) { //use firstLineIdent for the first row
1113 if (this == parentView.getView(0)) {
1114 adjustment = firstLineIndent;
1115 }
1116 }
1117 return (short) (super .getLeftInset() + adjustment);
1118 }
1119
1120 protected short getBottomInset() {
1121 return (short) (super .getBottomInset() + ((minorRequest != null) ? minorRequest.preferred
1122 : 0)
1123 * lineSpacing);
1124 }
1125
1126 final static int SPACE_ADDON = 0;
1127 final static int SPACE_ADDON_LEFTOVER_END = 1;
1128 final static int START_JUSTIFIABLE = 2;
1129 //this should be the last index in justificationData
1130 final static int END_JUSTIFIABLE = 3;
1131
1132 int justificationData[] = null;
1133 }
1134
1135 }
|