1. /*
  2. * @(#)ParagraphView.java 1.89 03/03/17
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.text;
  8. import java.awt.*;
  9. import java.awt.font.TextAttribute;
  10. import javax.swing.event.*;
  11. import javax.swing.SizeRequirements;
  12. /**
  13. * View of a simple line-wrapping paragraph that supports
  14. * multiple fonts, colors, components, icons, etc. It is
  15. * basically a vertical box with a margin around it. The
  16. * contents of the box are a bunch of rows which are special
  17. * horizontal boxes. This view creates a collection of
  18. * views that represent the child elements of the paragraph
  19. * element. Each of these views are placed into a row
  20. * directly if they will fit, otherwise the <code>breakView</code>
  21. * method is called to try and carve the view into pieces
  22. * that fit.
  23. *
  24. * @author Timothy Prinzing
  25. * @author Scott Violet
  26. * @version 1.89 03/17/03
  27. * @see View
  28. */
  29. public class ParagraphView extends FlowView implements TabExpander {
  30. /**
  31. * Constructs a <code>ParagraphView</code> for the given element.
  32. *
  33. * @param elem the element that this view is responsible for
  34. */
  35. public ParagraphView(Element elem) {
  36. super(elem, View.Y_AXIS);
  37. setPropertiesFromAttributes();
  38. Document doc = elem.getDocument();
  39. Object i18nFlag = doc.getProperty(AbstractDocument.I18NProperty);
  40. if ((i18nFlag != null) && i18nFlag.equals(Boolean.TRUE)) {
  41. try {
  42. if (i18nStrategy == null) {
  43. // the classname should probably come from a property file.
  44. String classname = "javax.swing.text.TextLayoutStrategy";
  45. ClassLoader loader = getClass().getClassLoader();
  46. if (loader != null) {
  47. i18nStrategy = loader.loadClass(classname);
  48. } else {
  49. i18nStrategy = Class.forName(classname);
  50. }
  51. }
  52. Object o = i18nStrategy.newInstance();
  53. if (o instanceof FlowStrategy) {
  54. strategy = (FlowStrategy) o;
  55. }
  56. } catch (Throwable e) {
  57. throw new StateInvariantError("ParagraphView: Can't create i18n strategy: "
  58. + e.getMessage());
  59. }
  60. }
  61. }
  62. /**
  63. * Sets the type of justification.
  64. *
  65. * @param j one of the following values:
  66. * <ul>
  67. * <li><code>StyleConstants.ALIGN_LEFT</code>
  68. * <li><code>StyleConstants.ALIGN_CENTER</code>
  69. * <li><code>StyleConstants.ALIGN_RIGHT</code>
  70. * </ul>
  71. */
  72. protected void setJustification(int j) {
  73. justification = j;
  74. }
  75. /**
  76. * Sets the line spacing.
  77. *
  78. * @param ls the value is a factor of the line hight
  79. */
  80. protected void setLineSpacing(float ls) {
  81. lineSpacing = ls;
  82. }
  83. /**
  84. * Sets the indent on the first line.
  85. *
  86. * @param fi the value in points
  87. */
  88. protected void setFirstLineIndent(float fi) {
  89. firstLineIndent = (int) fi;
  90. }
  91. /**
  92. * Set the cached properties from the attributes.
  93. */
  94. protected void setPropertiesFromAttributes() {
  95. AttributeSet attr = getAttributes();
  96. if (attr != null) {
  97. setParagraphInsets(attr);
  98. Integer a = (Integer)attr.getAttribute(StyleConstants.Alignment);
  99. int alignment;
  100. if (a == null) {
  101. Document doc = getElement().getDocument();
  102. Object o = doc.getProperty(TextAttribute.RUN_DIRECTION);
  103. if ((o != null) && o.equals(TextAttribute.RUN_DIRECTION_RTL)) {
  104. alignment = StyleConstants.ALIGN_RIGHT;
  105. } else {
  106. alignment = StyleConstants.ALIGN_LEFT;
  107. }
  108. } else {
  109. alignment = a.intValue();
  110. }
  111. setJustification(alignment);
  112. setLineSpacing(StyleConstants.getLineSpacing(attr));
  113. setFirstLineIndent(StyleConstants.getFirstLineIndent(attr));
  114. }
  115. }
  116. /**
  117. * Returns the number of views that this view is
  118. * responsible for.
  119. * The child views of the paragraph are rows which
  120. * have been used to arrange pieces of the <code>View</code>s
  121. * that represent the child elements. This is the number
  122. * of views that have been tiled in two dimensions,
  123. * and should be equivalent to the number of child elements
  124. * to the element this view is responsible for.
  125. *
  126. * @return the number of views that this <code>ParagraphView</code>
  127. * is responsible for
  128. */
  129. protected int getLayoutViewCount() {
  130. return layoutPool.getViewCount();
  131. }
  132. /**
  133. * Returns the view at a given <code>index</code>.
  134. * The child views of the paragraph are rows which
  135. * have been used to arrange pieces of the <code>Views</code>
  136. * that represent the child elements. This methods returns
  137. * the view responsible for the child element index
  138. * (prior to breaking). These are the Views that were
  139. * produced from a factory (to represent the child
  140. * elements) and used for layout.
  141. *
  142. * @param index the <code>index</code> of the desired view
  143. * @return the view at <code>index</code>
  144. */
  145. protected View getLayoutView(int index) {
  146. return layoutPool.getView(index);
  147. }
  148. /**
  149. * Adjusts the given row if possible to fit within the
  150. * layout span. By default this will try to find the
  151. * highest break weight possible nearest the end of
  152. * the row. If a forced break is encountered, the
  153. * break will be positioned there.
  154. * <p>
  155. * This is meant for internal usage, and should not be used directly.
  156. *
  157. * @param r the row to adjust to the current layout
  158. * span
  159. * @param desiredSpan the current layout span >= 0
  160. * @param x the location r starts at
  161. */
  162. protected void adjustRow(Row r, int desiredSpan, int x) {
  163. }
  164. /**
  165. * Returns the next visual position for the cursor, in
  166. * either the east or west direction.
  167. * Overridden from <code>CompositeView</code>.
  168. * @param pos position into the model
  169. * @param b either <code>Position.Bias.Forward</code> or
  170. * <code>Position.Bias.Backward</code>
  171. * @param a the allocated region to render into
  172. * @param direction either <code>SwingConstants.NORTH</code>
  173. * or <code>SwingConstants.SOUTH</code>
  174. * @param biasRet an array containing the bias that were checked
  175. * in this method
  176. * @return the location in the model that represents the
  177. * next location visual position
  178. */
  179. protected int getNextNorthSouthVisualPositionFrom(int pos, Position.Bias b,
  180. Shape a, int direction,
  181. Position.Bias[] biasRet)
  182. throws BadLocationException {
  183. int vIndex;
  184. if(pos == -1) {
  185. vIndex = (direction == NORTH) ?
  186. getViewCount() - 1 : 0;
  187. }
  188. else {
  189. if(b == Position.Bias.Backward && pos > 0) {
  190. vIndex = getViewIndexAtPosition(pos - 1);
  191. }
  192. else {
  193. vIndex = getViewIndexAtPosition(pos);
  194. }
  195. if(direction == NORTH) {
  196. if(vIndex == 0) {
  197. return -1;
  198. }
  199. vIndex--;
  200. }
  201. else if(++vIndex >= getViewCount()) {
  202. return -1;
  203. }
  204. }
  205. // vIndex gives index of row to look in.
  206. JTextComponent text = (JTextComponent)getContainer();
  207. Caret c = text.getCaret();
  208. Point magicPoint;
  209. magicPoint = (c != null) ? c.getMagicCaretPosition() : null;
  210. int x;
  211. if(magicPoint == null) {
  212. Shape posBounds = text.getUI().modelToView(text, pos, b);
  213. if(posBounds == null) {
  214. x = 0;
  215. }
  216. else {
  217. x = posBounds.getBounds().x;
  218. }
  219. }
  220. else {
  221. x = magicPoint.x;
  222. }
  223. return getClosestPositionTo(pos, b, a, direction, biasRet, vIndex, x);
  224. }
  225. /**
  226. * Returns the closest model position to <code>x</code>.
  227. * <code>rowIndex</code> gives the index of the view that corresponds
  228. * that should be looked in.
  229. * @param pos position into the model
  230. * @param a the allocated region to render into
  231. * @param direction one of the following values:
  232. * <ul>
  233. * <li><code>SwingConstants.NORTH</code>
  234. * <li><code>SwingConstants.SOUTH</code>
  235. * </ul>
  236. * @param biasRet an array containing the bias that were checked
  237. * in this method
  238. * @param rowIndex the index of the view
  239. * @param x the x coordinate of interest
  240. * @return the closest model position to <code>x</code>
  241. */
  242. // NOTE: This will not properly work if ParagraphView contains
  243. // other ParagraphViews. It won't raise, but this does not message
  244. // the children views with getNextVisualPositionFrom.
  245. protected int getClosestPositionTo(int pos, Position.Bias b, Shape a,
  246. int direction, Position.Bias[] biasRet,
  247. int rowIndex, int x)
  248. throws BadLocationException {
  249. JTextComponent text = (JTextComponent)getContainer();
  250. Document doc = getDocument();
  251. AbstractDocument aDoc = (doc instanceof AbstractDocument) ?
  252. (AbstractDocument)doc : null;
  253. View row = getView(rowIndex);
  254. int lastPos = -1;
  255. // This could be made better to check backward positions too.
  256. biasRet[0] = Position.Bias.Forward;
  257. for(int vc = 0, numViews = row.getViewCount(); vc < numViews; vc++) {
  258. View v = row.getView(vc);
  259. int start = v.getStartOffset();
  260. boolean ltr = (aDoc != null) ? aDoc.isLeftToRight
  261. (start, start + 1) : true;
  262. if(ltr) {
  263. lastPos = start;
  264. for(int end = v.getEndOffset(); lastPos < end; lastPos++) {
  265. if(text.modelToView(lastPos).getBounds().x >= x) {
  266. return lastPos;
  267. }
  268. }
  269. lastPos--;
  270. }
  271. else {
  272. for(lastPos = v.getEndOffset() - 1; lastPos >= start;
  273. lastPos--) {
  274. if(text.modelToView(lastPos).getBounds().x >= x) {
  275. return lastPos;
  276. }
  277. }
  278. lastPos++;
  279. }
  280. }
  281. if(lastPos == -1) {
  282. return getStartOffset();
  283. }
  284. return lastPos;
  285. }
  286. /**
  287. * Determines in which direction the next view lays.
  288. * Consider the <code>View</code> at index n.
  289. * Typically the <code>View</code>s are layed out
  290. * from left to right, so that the <code>View</code>
  291. * to the EAST will be at index n + 1, and the
  292. * <code>View</code> to the WEST will be at index n - 1.
  293. * In certain situations, such as with bidirectional text,
  294. * it is possible that the <code>View</code> to EAST is not
  295. * at index n + 1, but rather at index n - 1,
  296. * or that the <code>View</code> to the WEST is not at
  297. * index n - 1, but index n + 1. In this case this method
  298. * would return true, indicating the <code>View</code>s are
  299. * layed out in descending order.
  300. * <p>
  301. * This will return true if the text is layed out right
  302. * to left at position, otherwise false.
  303. *
  304. * @param position position into the model
  305. * @param bias either <code>Position.Bias.Forward</code> or
  306. * <code>Position.Bias.Backward</code>
  307. * @return true if the text is layed out right to left at
  308. * position, otherwise false.
  309. */
  310. protected boolean flipEastAndWestAtEnds(int position,
  311. Position.Bias bias) {
  312. Document doc = getDocument();
  313. if(doc instanceof AbstractDocument &&
  314. !((AbstractDocument)doc).isLeftToRight(getStartOffset(),
  315. getStartOffset() + 1)) {
  316. return true;
  317. }
  318. return false;
  319. }
  320. // --- FlowView methods ---------------------------------------------
  321. /**
  322. * Fetches the constraining span to flow against for
  323. * the given child index.
  324. * @param index the index of the view being queried
  325. * @return the constraining span for the given view at
  326. * <code>index</code>
  327. */
  328. public int getFlowSpan(int index) {
  329. View child = getView(index);
  330. int adjust = 0;
  331. if (child instanceof Row) {
  332. Row row = (Row) child;
  333. adjust = row.getLeftInset() + row.getRightInset();
  334. }
  335. int span = layoutSpan - adjust;
  336. return span;
  337. }
  338. /**
  339. * Fetches the location along the flow axis that the
  340. * flow span will start at.
  341. * @param index the index of the view being queried
  342. * @return the location for the given view at
  343. * <code>index</code>
  344. */
  345. public int getFlowStart(int index) {
  346. View child = getView(index);
  347. int adjust = 0;
  348. if (child instanceof Row) {
  349. Row row = (Row) child;
  350. adjust = row.getLeftInset();
  351. }
  352. return tabBase + adjust;
  353. }
  354. /**
  355. * Create a <code>View</code> that should be used to hold a
  356. * a row's worth of children in a flow.
  357. * @return the new <code>View</code>
  358. */
  359. protected View createRow() {
  360. return new Row(getElement());
  361. }
  362. // --- TabExpander methods ------------------------------------------
  363. /**
  364. * Returns the next tab stop position given a reference position.
  365. * This view implements the tab coordinate system, and calls
  366. * <code>getTabbedSpan</code> on the logical children in the process
  367. * of layout to determine the desired span of the children. The
  368. * logical children can delegate their tab expansion upward to
  369. * the paragraph which knows how to expand tabs.
  370. * <code>LabelView</code> is an example of a view that delegates
  371. * its tab expansion needs upward to the paragraph.
  372. * <p>
  373. * This is implemented to try and locate a <code>TabSet</code>
  374. * in the paragraph element's attribute set. If one can be
  375. * found, its settings will be used, otherwise a default expansion
  376. * will be provided. The base location for for tab expansion
  377. * is the left inset from the paragraphs most recent allocation
  378. * (which is what the layout of the children is based upon).
  379. *
  380. * @param x the X reference position
  381. * @param tabOffset the position within the text stream
  382. * that the tab occurred at >= 0
  383. * @return the trailing end of the tab expansion >= 0
  384. * @see TabSet
  385. * @see TabStop
  386. * @see LabelView
  387. */
  388. public float nextTabStop(float x, int tabOffset) {
  389. // If the text isn't left justified, offset by 10 pixels!
  390. if(justification != StyleConstants.ALIGN_LEFT)
  391. return x + 10.0f;
  392. x -= tabBase;
  393. TabSet tabs = getTabSet();
  394. if(tabs == null) {
  395. // a tab every 72 pixels.
  396. return (float)(tabBase + (((int)x / 72 + 1) * 72));
  397. }
  398. TabStop tab = tabs.getTabAfter(x + .01f);
  399. if(tab == null) {
  400. // no tab, do a default of 5 pixels.
  401. // Should this cause a wrapping of the line?
  402. return tabBase + x + 5.0f;
  403. }
  404. int alignment = tab.getAlignment();
  405. int offset;
  406. switch(alignment) {
  407. default:
  408. case TabStop.ALIGN_LEFT:
  409. // Simple case, left tab.
  410. return tabBase + tab.getPosition();
  411. case TabStop.ALIGN_BAR:
  412. // PENDING: what does this mean?
  413. return tabBase + tab.getPosition();
  414. case TabStop.ALIGN_RIGHT:
  415. case TabStop.ALIGN_CENTER:
  416. offset = findOffsetToCharactersInString(tabChars,
  417. tabOffset + 1);
  418. break;
  419. case TabStop.ALIGN_DECIMAL:
  420. offset = findOffsetToCharactersInString(tabDecimalChars,
  421. tabOffset + 1);
  422. break;
  423. }
  424. if (offset == -1) {
  425. offset = getEndOffset();
  426. }
  427. float charsSize = getPartialSize(tabOffset + 1, offset);
  428. switch(alignment) {
  429. case TabStop.ALIGN_RIGHT:
  430. case TabStop.ALIGN_DECIMAL:
  431. // right and decimal are treated the same way, the new
  432. // position will be the location of the tab less the
  433. // partialSize.
  434. return tabBase + Math.max(x, tab.getPosition() - charsSize);
  435. case TabStop.ALIGN_CENTER:
  436. // Similar to right, but half the partialSize.
  437. return tabBase + Math.max(x, tab.getPosition() - charsSize / 2.0f);
  438. }
  439. // will never get here!
  440. return x;
  441. }
  442. /**
  443. * Gets the <code>Tabset</code> to be used in calculating tabs.
  444. *
  445. * @return the <code>TabSet</code>
  446. */
  447. protected TabSet getTabSet() {
  448. return StyleConstants.getTabSet(getElement().getAttributes());
  449. }
  450. /**
  451. * Returns the size used by the views between
  452. * <code>startOffset</code> and <code>endOffset</code>.
  453. * This uses <code>getPartialView</code> to calculate the
  454. * size if the child view implements the
  455. * <code>TabableView</code> interface. If a
  456. * size is needed and a <code>View</code> does not implement
  457. * the <code>TabableView</code> interface,
  458. * the <code>preferredSpan</code> will be used.
  459. *
  460. * @param startOffset the starting document offset >= 0
  461. * @param endOffset the ending document offset >= startOffset
  462. * @return the size >= 0
  463. */
  464. protected float getPartialSize(int startOffset, int endOffset) {
  465. float size = 0.0f;
  466. int viewIndex;
  467. int numViews = getViewCount();
  468. View view;
  469. int viewEnd;
  470. int tempEnd;
  471. // Have to search layoutPool!
  472. // PENDING: when ParagraphView supports breaking location
  473. // into layoutPool will have to change!
  474. viewIndex = getElement().getElementIndex(startOffset);
  475. numViews = layoutPool.getViewCount();
  476. while(startOffset < endOffset && viewIndex < numViews) {
  477. view = layoutPool.getView(viewIndex++);
  478. viewEnd = view.getEndOffset();
  479. tempEnd = Math.min(endOffset, viewEnd);
  480. if(view instanceof TabableView)
  481. size += ((TabableView)view).getPartialSpan(startOffset, tempEnd);
  482. else if(startOffset == view.getStartOffset() &&
  483. tempEnd == view.getEndOffset())
  484. size += view.getPreferredSpan(View.X_AXIS);
  485. else
  486. // PENDING: should we handle this better?
  487. return 0.0f;
  488. startOffset = viewEnd;
  489. }
  490. return size;
  491. }
  492. /**
  493. * Finds the next character in the document with a character in
  494. * <code>string</code>, starting at offset <code>start</code>. If
  495. * there are no characters found, -1 will be returned.
  496. *
  497. * @param string the string of characters
  498. * @param start where to start in the model >= 0
  499. * @return the document offset, or -1 if no characters found
  500. */
  501. protected int findOffsetToCharactersInString(char[] string,
  502. int start) {
  503. int stringLength = string.length;
  504. int end = getEndOffset();
  505. Segment seg = new Segment();
  506. try {
  507. getDocument().getText(start, end - start, seg);
  508. } catch (BadLocationException ble) {
  509. return -1;
  510. }
  511. for(int counter = seg.offset, maxCounter = seg.offset + seg.count;
  512. counter < maxCounter; counter++) {
  513. char currentChar = seg.array[counter];
  514. for(int subCounter = 0; subCounter < stringLength;
  515. subCounter++) {
  516. if(currentChar == string[subCounter])
  517. return counter - seg.offset + start;
  518. }
  519. }
  520. // No match.
  521. return -1;
  522. }
  523. /**
  524. * Returns where the tabs are calculated from.
  525. * @return where tabs are calculated from
  526. */
  527. protected float getTabBase() {
  528. return (float)tabBase;
  529. }
  530. // ---- View methods ----------------------------------------------------
  531. /**
  532. * Renders using the given rendering surface and area on that
  533. * surface. This is implemented to delgate to the superclass
  534. * after stashing the base coordinate for tab calculations.
  535. *
  536. * @param g the rendering surface to use
  537. * @param a the allocated region to render into
  538. * @see View#paint
  539. */
  540. public void paint(Graphics g, Shape a) {
  541. Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
  542. tabBase = alloc.x + getLeftInset();
  543. super.paint(g, a);
  544. }
  545. /**
  546. * Determines the desired alignment for this view along an
  547. * axis. This is implemented to give the alignment to the
  548. * center of the first row along the y axis, and the default
  549. * along the x axis.
  550. *
  551. * @param axis may be either <code>View.X_AXIS</code> or
  552. * <code>View.Y_AXIS</code>
  553. * @return the desired alignment. This should be a value
  554. * between 0.0 and 1.0 inclusive, where 0 indicates alignment at the
  555. * origin and 1.0 indicates alignment to the full span
  556. * away from the origin. An alignment of 0.5 would be the
  557. * center of the view.
  558. */
  559. public float getAlignment(int axis) {
  560. switch (axis) {
  561. case Y_AXIS:
  562. float a = 0.5f;
  563. if (getViewCount() != 0) {
  564. int paragraphSpan = (int) getPreferredSpan(View.Y_AXIS);
  565. View v = getView(0);
  566. int rowSpan = (int) v.getPreferredSpan(View.Y_AXIS);
  567. a = (paragraphSpan != 0) ? ((float)(rowSpan / 2)) / paragraphSpan : 0;
  568. }
  569. return a;
  570. case X_AXIS:
  571. return 0.5f;
  572. default:
  573. throw new IllegalArgumentException("Invalid axis: " + axis);
  574. }
  575. }
  576. /**
  577. * Breaks this view on the given axis at the given length.
  578. * <p>
  579. * <code>ParagraphView</code> instances are breakable
  580. * along the <code>Y_AXIS</code> only, and only if
  581. * <code>len</code> is after the first line.
  582. *
  583. * @param axis may be either <code>View.X_AXIS</code>
  584. * or <code>View.Y_AXIS</code>
  585. * @param len specifies where a potential break is desired
  586. * along the given axis >= 0
  587. * @param a the current allocation of the view
  588. * @return the fragment of the view that represents the
  589. * given span, if the view can be broken; if the view
  590. * doesn't support breaking behavior, the view itself is
  591. * returned
  592. * @see View#breakView
  593. */
  594. public View breakView(int axis, float len, Shape a) {
  595. if(axis == View.Y_AXIS) {
  596. if(a != null) {
  597. Rectangle alloc = a.getBounds();
  598. setSize(alloc.width, alloc.height);
  599. }
  600. // Determine what row to break on.
  601. // PENDING(prinz) add break support
  602. return this;
  603. }
  604. return this;
  605. }
  606. /**
  607. * Gets the break weight for a given location.
  608. * <p>
  609. * <code>ParagraphView</code> instances are breakable
  610. * along the <code>Y_AXIS</code> only, and only if
  611. * <code>len</code> is after the first row. If the length
  612. * is less than one row, a value of <code>BadBreakWeight</code>
  613. * is returned.
  614. *
  615. * @param axis may be either <code>View.X_AXIS</code>
  616. * or <code>View.Y_AXIS</code>
  617. * @param len specifies where a potential break is desired >= 0
  618. * @return a value indicating the attractiveness of breaking here;
  619. * either <code>GoodBreakWeight</code> or <code>BadBreakWeight</code>
  620. * @see View#getBreakWeight
  621. */
  622. public int getBreakWeight(int axis, float len) {
  623. if(axis == View.Y_AXIS) {
  624. // PENDING(prinz) make this return a reasonable value
  625. // when paragraph breaking support is re-implemented.
  626. // If less than one row, bad weight value should be
  627. // returned.
  628. //return GoodBreakWeight;
  629. return BadBreakWeight;
  630. }
  631. return BadBreakWeight;
  632. }
  633. /**
  634. * Gives notification from the document that attributes were changed
  635. * in a location that this view is responsible for.
  636. *
  637. * @param changes the change information from the
  638. * associated document
  639. * @param a the current allocation of the view
  640. * @param f the factory to use to rebuild if the view has children
  641. * @see View#changedUpdate
  642. */
  643. public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
  644. // update any property settings stored, and layout should be
  645. // recomputed
  646. setPropertiesFromAttributes();
  647. layoutChanged(X_AXIS);
  648. layoutChanged(Y_AXIS);
  649. super.changedUpdate(changes, a, f);
  650. }
  651. // --- variables -----------------------------------------------
  652. private int justification;
  653. private float lineSpacing;
  654. /** Indentation for the first line, from the left inset. */
  655. protected int firstLineIndent = 0;
  656. /**
  657. * Used by the TabExpander functionality to determine
  658. * where to base the tab calculations. This is basically
  659. * the location of the left side of the paragraph.
  660. */
  661. private int tabBase;
  662. /**
  663. * Used to create an i18n-based layout strategy
  664. */
  665. static Class i18nStrategy;
  666. /** Used for searching for a tab. */
  667. static char[] tabChars;
  668. /** Used for searching for a tab or decimal character. */
  669. static char[] tabDecimalChars;
  670. static {
  671. tabChars = new char[1];
  672. tabChars[0] = '\t';
  673. tabDecimalChars = new char[2];
  674. tabDecimalChars[0] = '\t';
  675. tabDecimalChars[1] = '.';
  676. }
  677. /**
  678. * Internally created view that has the purpose of holding
  679. * the views that represent the children of the paragraph
  680. * that have been arranged in rows.
  681. */
  682. class Row extends BoxView {
  683. Row(Element elem) {
  684. super(elem, View.X_AXIS);
  685. }
  686. /**
  687. * This is reimplemented to do nothing since the
  688. * paragraph fills in the row with its needed
  689. * children.
  690. */
  691. protected void loadChildren(ViewFactory f) {
  692. }
  693. /**
  694. * Fetches the attributes to use when rendering. This view
  695. * isn't directly responsible for an element so it returns
  696. * the outer classes attributes.
  697. */
  698. public AttributeSet getAttributes() {
  699. View p = getParent();
  700. return (p != null) ? p.getAttributes() : null;
  701. }
  702. public float getAlignment(int axis) {
  703. if (axis == View.X_AXIS) {
  704. switch (justification) {
  705. case StyleConstants.ALIGN_LEFT:
  706. return 0;
  707. case StyleConstants.ALIGN_RIGHT:
  708. return 1;
  709. case StyleConstants.ALIGN_CENTER:
  710. case StyleConstants.ALIGN_JUSTIFIED:
  711. return 0.5f;
  712. }
  713. }
  714. return super.getAlignment(axis);
  715. }
  716. /**
  717. * Provides a mapping from the document model coordinate space
  718. * to the coordinate space of the view mapped to it. This is
  719. * implemented to let the superclass find the position along
  720. * the major axis and the allocation of the row is used
  721. * along the minor axis, so that even though the children
  722. * are different heights they all get the same caret height.
  723. *
  724. * @param pos the position to convert
  725. * @param a the allocated region to render into
  726. * @return the bounding box of the given position
  727. * @exception BadLocationException if the given position does not represent a
  728. * valid location in the associated document
  729. * @see View#modelToView
  730. */
  731. public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
  732. Rectangle r = a.getBounds();
  733. View v = getViewAtPosition(pos, r);
  734. if ((v != null) && (!v.getElement().isLeaf())) {
  735. // Don't adjust the height if the view represents a branch.
  736. return super.modelToView(pos, a, b);
  737. }
  738. r = a.getBounds();
  739. int height = r.height;
  740. int y = r.y;
  741. Shape loc = super.modelToView(pos, a, b);
  742. r = loc.getBounds();
  743. r.height = height;
  744. r.y = y;
  745. return r;
  746. }
  747. /**
  748. * Range represented by a row in the paragraph is only
  749. * a subset of the total range of the paragraph element.
  750. * @see View#getRange
  751. */
  752. public int getStartOffset() {
  753. int offs = Integer.MAX_VALUE;
  754. int n = getViewCount();
  755. for (int i = 0; i < n; i++) {
  756. View v = getView(i);
  757. offs = Math.min(offs, v.getStartOffset());
  758. }
  759. return offs;
  760. }
  761. public int getEndOffset() {
  762. int offs = 0;
  763. int n = getViewCount();
  764. for (int i = 0; i < n; i++) {
  765. View v = getView(i);
  766. offs = Math.max(offs, v.getEndOffset());
  767. }
  768. return offs;
  769. }
  770. /**
  771. * Perform layout for the minor axis of the box (i.e. the
  772. * axis orthoginal to the axis that it represents). The results
  773. * of the layout should be placed in the given arrays which represent
  774. * the allocations to the children along the minor axis.
  775. * <p>
  776. * This is implemented to do a baseline layout of the children
  777. * by calling BoxView.baselineLayout.
  778. *
  779. * @param targetSpan the total span given to the view, which
  780. * whould be used to layout the children.
  781. * @param axis the axis being layed out.
  782. * @param offsets the offsets from the origin of the view for
  783. * each of the child views. This is a return value and is
  784. * filled in by the implementation of this method.
  785. * @param spans the span of each child view. This is a return
  786. * value and is filled in by the implementation of this method.
  787. * @return the offset and span for each child view in the
  788. * offsets and spans parameters
  789. */
  790. protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
  791. baselineLayout(targetSpan, axis, offsets, spans);
  792. }
  793. protected SizeRequirements calculateMinorAxisRequirements(int axis,
  794. SizeRequirements r) {
  795. return baselineRequirements(axis, r);
  796. }
  797. /**
  798. * Fetches the child view index representing the given position in
  799. * the model.
  800. *
  801. * @param pos the position >= 0
  802. * @return index of the view representing the given position, or
  803. * -1 if no view represents that position
  804. */
  805. protected int getViewIndexAtPosition(int pos) {
  806. // This is expensive, but are views are not necessarily layed
  807. // out in model order.
  808. if(pos < getStartOffset() || pos >= getEndOffset())
  809. return -1;
  810. for(int counter = getViewCount() - 1; counter >= 0; counter--) {
  811. View v = getView(counter);
  812. if(pos >= v.getStartOffset() &&
  813. pos < v.getEndOffset()) {
  814. return counter;
  815. }
  816. }
  817. return -1;
  818. }
  819. /**
  820. * Gets the left inset.
  821. *
  822. * @return the inset
  823. */
  824. protected short getLeftInset() {
  825. View parentView;
  826. int adjustment = 0;
  827. if ((parentView = getParent()) != null) { //use firstLineIdent for the first row
  828. if (this == parentView.getView(0)) {
  829. adjustment = firstLineIndent;
  830. }
  831. }
  832. return (short)(super.getLeftInset() + adjustment);
  833. }
  834. protected short getBottomInset() {
  835. return (short)(super.getBottomInset() +
  836. ((minorRequest != null) ? minorRequest.preferred : 0) *
  837. lineSpacing);
  838. }
  839. }
  840. }