1. /*
  2. * @(#)ParagraphView.java 1.92 03/12/19
  3. *
  4. * Copyright 2004 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.90 04/16/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;
  213. try {
  214. posBounds = text.getUI().modelToView(text, pos, b);
  215. } catch (BadLocationException exc) {
  216. posBounds = null;
  217. }
  218. if(posBounds == null) {
  219. x = 0;
  220. }
  221. else {
  222. x = posBounds.getBounds().x;
  223. }
  224. }
  225. else {
  226. x = magicPoint.x;
  227. }
  228. return getClosestPositionTo(pos, b, a, direction, biasRet, vIndex, x);
  229. }
  230. /**
  231. * Returns the closest model position to <code>x</code>.
  232. * <code>rowIndex</code> gives the index of the view that corresponds
  233. * that should be looked in.
  234. * @param pos position into the model
  235. * @param a the allocated region to render into
  236. * @param direction one of the following values:
  237. * <ul>
  238. * <li><code>SwingConstants.NORTH</code>
  239. * <li><code>SwingConstants.SOUTH</code>
  240. * </ul>
  241. * @param biasRet an array containing the bias that were checked
  242. * in this method
  243. * @param rowIndex the index of the view
  244. * @param x the x coordinate of interest
  245. * @return the closest model position to <code>x</code>
  246. */
  247. // NOTE: This will not properly work if ParagraphView contains
  248. // other ParagraphViews. It won't raise, but this does not message
  249. // the children views with getNextVisualPositionFrom.
  250. protected int getClosestPositionTo(int pos, Position.Bias b, Shape a,
  251. int direction, Position.Bias[] biasRet,
  252. int rowIndex, int x)
  253. throws BadLocationException {
  254. JTextComponent text = (JTextComponent)getContainer();
  255. Document doc = getDocument();
  256. AbstractDocument aDoc = (doc instanceof AbstractDocument) ?
  257. (AbstractDocument)doc : null;
  258. View row = getView(rowIndex);
  259. int lastPos = -1;
  260. // This could be made better to check backward positions too.
  261. biasRet[0] = Position.Bias.Forward;
  262. for(int vc = 0, numViews = row.getViewCount(); vc < numViews; vc++) {
  263. View v = row.getView(vc);
  264. int start = v.getStartOffset();
  265. boolean ltr = (aDoc != null) ? aDoc.isLeftToRight
  266. (start, start + 1) : true;
  267. if(ltr) {
  268. lastPos = start;
  269. for(int end = v.getEndOffset(); lastPos < end; lastPos++) {
  270. float xx = text.modelToView(lastPos).getBounds().x;
  271. if(xx >= x) {
  272. while (++lastPos < end &&
  273. text.modelToView(lastPos).getBounds().x == xx) {
  274. }
  275. return --lastPos;
  276. }
  277. }
  278. lastPos--;
  279. }
  280. else {
  281. for(lastPos = v.getEndOffset() - 1; lastPos >= start;
  282. lastPos--) {
  283. float xx = text.modelToView(lastPos).getBounds().x;
  284. if(xx >= x) {
  285. while (--lastPos >= start &&
  286. text.modelToView(lastPos).getBounds().x == xx) {
  287. }
  288. return ++lastPos;
  289. }
  290. }
  291. lastPos++;
  292. }
  293. }
  294. if(lastPos == -1) {
  295. return getStartOffset();
  296. }
  297. return lastPos;
  298. }
  299. /**
  300. * Determines in which direction the next view lays.
  301. * Consider the <code>View</code> at index n.
  302. * Typically the <code>View</code>s are layed out
  303. * from left to right, so that the <code>View</code>
  304. * to the EAST will be at index n + 1, and the
  305. * <code>View</code> to the WEST will be at index n - 1.
  306. * In certain situations, such as with bidirectional text,
  307. * it is possible that the <code>View</code> to EAST is not
  308. * at index n + 1, but rather at index n - 1,
  309. * or that the <code>View</code> to the WEST is not at
  310. * index n - 1, but index n + 1. In this case this method
  311. * would return true, indicating the <code>View</code>s are
  312. * layed out in descending order.
  313. * <p>
  314. * This will return true if the text is layed out right
  315. * to left at position, otherwise false.
  316. *
  317. * @param position position into the model
  318. * @param bias either <code>Position.Bias.Forward</code> or
  319. * <code>Position.Bias.Backward</code>
  320. * @return true if the text is layed out right to left at
  321. * position, otherwise false.
  322. */
  323. protected boolean flipEastAndWestAtEnds(int position,
  324. Position.Bias bias) {
  325. Document doc = getDocument();
  326. if(doc instanceof AbstractDocument &&
  327. !((AbstractDocument)doc).isLeftToRight(getStartOffset(),
  328. getStartOffset() + 1)) {
  329. return true;
  330. }
  331. return false;
  332. }
  333. // --- FlowView methods ---------------------------------------------
  334. /**
  335. * Fetches the constraining span to flow against for
  336. * the given child index.
  337. * @param index the index of the view being queried
  338. * @return the constraining span for the given view at
  339. * <code>index</code>
  340. */
  341. public int getFlowSpan(int index) {
  342. View child = getView(index);
  343. int adjust = 0;
  344. if (child instanceof Row) {
  345. Row row = (Row) child;
  346. adjust = row.getLeftInset() + row.getRightInset();
  347. }
  348. int span = layoutSpan - adjust;
  349. return span;
  350. }
  351. /**
  352. * Fetches the location along the flow axis that the
  353. * flow span will start at.
  354. * @param index the index of the view being queried
  355. * @return the location for the given view at
  356. * <code>index</code>
  357. */
  358. public int getFlowStart(int index) {
  359. View child = getView(index);
  360. int adjust = 0;
  361. if (child instanceof Row) {
  362. Row row = (Row) child;
  363. adjust = row.getLeftInset();
  364. }
  365. return tabBase + adjust;
  366. }
  367. /**
  368. * Create a <code>View</code> that should be used to hold a
  369. * a row's worth of children in a flow.
  370. * @return the new <code>View</code>
  371. */
  372. protected View createRow() {
  373. return new Row(getElement());
  374. }
  375. // --- TabExpander methods ------------------------------------------
  376. /**
  377. * Returns the next tab stop position given a reference position.
  378. * This view implements the tab coordinate system, and calls
  379. * <code>getTabbedSpan</code> on the logical children in the process
  380. * of layout to determine the desired span of the children. The
  381. * logical children can delegate their tab expansion upward to
  382. * the paragraph which knows how to expand tabs.
  383. * <code>LabelView</code> is an example of a view that delegates
  384. * its tab expansion needs upward to the paragraph.
  385. * <p>
  386. * This is implemented to try and locate a <code>TabSet</code>
  387. * in the paragraph element's attribute set. If one can be
  388. * found, its settings will be used, otherwise a default expansion
  389. * will be provided. The base location for for tab expansion
  390. * is the left inset from the paragraphs most recent allocation
  391. * (which is what the layout of the children is based upon).
  392. *
  393. * @param x the X reference position
  394. * @param tabOffset the position within the text stream
  395. * that the tab occurred at >= 0
  396. * @return the trailing end of the tab expansion >= 0
  397. * @see TabSet
  398. * @see TabStop
  399. * @see LabelView
  400. */
  401. public float nextTabStop(float x, int tabOffset) {
  402. // If the text isn't left justified, offset by 10 pixels!
  403. if(justification != StyleConstants.ALIGN_LEFT)
  404. return x + 10.0f;
  405. x -= tabBase;
  406. TabSet tabs = getTabSet();
  407. if(tabs == null) {
  408. // a tab every 72 pixels.
  409. return (float)(tabBase + (((int)x / 72 + 1) * 72));
  410. }
  411. TabStop tab = tabs.getTabAfter(x + .01f);
  412. if(tab == null) {
  413. // no tab, do a default of 5 pixels.
  414. // Should this cause a wrapping of the line?
  415. return tabBase + x + 5.0f;
  416. }
  417. int alignment = tab.getAlignment();
  418. int offset;
  419. switch(alignment) {
  420. default:
  421. case TabStop.ALIGN_LEFT:
  422. // Simple case, left tab.
  423. return tabBase + tab.getPosition();
  424. case TabStop.ALIGN_BAR:
  425. // PENDING: what does this mean?
  426. return tabBase + tab.getPosition();
  427. case TabStop.ALIGN_RIGHT:
  428. case TabStop.ALIGN_CENTER:
  429. offset = findOffsetToCharactersInString(tabChars,
  430. tabOffset + 1);
  431. break;
  432. case TabStop.ALIGN_DECIMAL:
  433. offset = findOffsetToCharactersInString(tabDecimalChars,
  434. tabOffset + 1);
  435. break;
  436. }
  437. if (offset == -1) {
  438. offset = getEndOffset();
  439. }
  440. float charsSize = getPartialSize(tabOffset + 1, offset);
  441. switch(alignment) {
  442. case TabStop.ALIGN_RIGHT:
  443. case TabStop.ALIGN_DECIMAL:
  444. // right and decimal are treated the same way, the new
  445. // position will be the location of the tab less the
  446. // partialSize.
  447. return tabBase + Math.max(x, tab.getPosition() - charsSize);
  448. case TabStop.ALIGN_CENTER:
  449. // Similar to right, but half the partialSize.
  450. return tabBase + Math.max(x, tab.getPosition() - charsSize / 2.0f);
  451. }
  452. // will never get here!
  453. return x;
  454. }
  455. /**
  456. * Gets the <code>Tabset</code> to be used in calculating tabs.
  457. *
  458. * @return the <code>TabSet</code>
  459. */
  460. protected TabSet getTabSet() {
  461. return StyleConstants.getTabSet(getElement().getAttributes());
  462. }
  463. /**
  464. * Returns the size used by the views between
  465. * <code>startOffset</code> and <code>endOffset</code>.
  466. * This uses <code>getPartialView</code> to calculate the
  467. * size if the child view implements the
  468. * <code>TabableView</code> interface. If a
  469. * size is needed and a <code>View</code> does not implement
  470. * the <code>TabableView</code> interface,
  471. * the <code>preferredSpan</code> will be used.
  472. *
  473. * @param startOffset the starting document offset >= 0
  474. * @param endOffset the ending document offset >= startOffset
  475. * @return the size >= 0
  476. */
  477. protected float getPartialSize(int startOffset, int endOffset) {
  478. float size = 0.0f;
  479. int viewIndex;
  480. int numViews = getViewCount();
  481. View view;
  482. int viewEnd;
  483. int tempEnd;
  484. // Have to search layoutPool!
  485. // PENDING: when ParagraphView supports breaking location
  486. // into layoutPool will have to change!
  487. viewIndex = getElement().getElementIndex(startOffset);
  488. numViews = layoutPool.getViewCount();
  489. while(startOffset < endOffset && viewIndex < numViews) {
  490. view = layoutPool.getView(viewIndex++);
  491. viewEnd = view.getEndOffset();
  492. tempEnd = Math.min(endOffset, viewEnd);
  493. if(view instanceof TabableView)
  494. size += ((TabableView)view).getPartialSpan(startOffset, tempEnd);
  495. else if(startOffset == view.getStartOffset() &&
  496. tempEnd == view.getEndOffset())
  497. size += view.getPreferredSpan(View.X_AXIS);
  498. else
  499. // PENDING: should we handle this better?
  500. return 0.0f;
  501. startOffset = viewEnd;
  502. }
  503. return size;
  504. }
  505. /**
  506. * Finds the next character in the document with a character in
  507. * <code>string</code>, starting at offset <code>start</code>. If
  508. * there are no characters found, -1 will be returned.
  509. *
  510. * @param string the string of characters
  511. * @param start where to start in the model >= 0
  512. * @return the document offset, or -1 if no characters found
  513. */
  514. protected int findOffsetToCharactersInString(char[] string,
  515. int start) {
  516. int stringLength = string.length;
  517. int end = getEndOffset();
  518. Segment seg = new Segment();
  519. try {
  520. getDocument().getText(start, end - start, seg);
  521. } catch (BadLocationException ble) {
  522. return -1;
  523. }
  524. for(int counter = seg.offset, maxCounter = seg.offset + seg.count;
  525. counter < maxCounter; counter++) {
  526. char currentChar = seg.array[counter];
  527. for(int subCounter = 0; subCounter < stringLength;
  528. subCounter++) {
  529. if(currentChar == string[subCounter])
  530. return counter - seg.offset + start;
  531. }
  532. }
  533. // No match.
  534. return -1;
  535. }
  536. /**
  537. * Returns where the tabs are calculated from.
  538. * @return where tabs are calculated from
  539. */
  540. protected float getTabBase() {
  541. return (float)tabBase;
  542. }
  543. // ---- View methods ----------------------------------------------------
  544. /**
  545. * Renders using the given rendering surface and area on that
  546. * surface. This is implemented to delgate to the superclass
  547. * after stashing the base coordinate for tab calculations.
  548. *
  549. * @param g the rendering surface to use
  550. * @param a the allocated region to render into
  551. * @see View#paint
  552. */
  553. public void paint(Graphics g, Shape a) {
  554. Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
  555. tabBase = alloc.x + getLeftInset();
  556. super.paint(g, a);
  557. // line with the negative firstLineIndent value needs
  558. // special handling
  559. if (firstLineIndent < 0) {
  560. Shape sh = getChildAllocation(0, a);
  561. if ((sh != null) && sh.intersects(alloc)) {
  562. int x = alloc.x + getLeftInset() + firstLineIndent;
  563. int y = alloc.y + getTopInset();
  564. Rectangle clip = g.getClipBounds();
  565. tempRect.x = x + getOffset(X_AXIS, 0);
  566. tempRect.y = y + getOffset(Y_AXIS, 0);
  567. tempRect.width = getSpan(X_AXIS, 0) - firstLineIndent;
  568. tempRect.height = getSpan(Y_AXIS, 0);
  569. if (tempRect.intersects(clip)) {
  570. tempRect.x = tempRect.x - firstLineIndent;
  571. paintChild(g, tempRect, 0);
  572. }
  573. }
  574. }
  575. }
  576. /**
  577. * Determines the desired alignment for this view along an
  578. * axis. This is implemented to give the alignment to the
  579. * center of the first row along the y axis, and the default
  580. * along the x axis.
  581. *
  582. * @param axis may be either <code>View.X_AXIS</code> or
  583. * <code>View.Y_AXIS</code>
  584. * @return the desired alignment. This should be a value
  585. * between 0.0 and 1.0 inclusive, where 0 indicates alignment at the
  586. * origin and 1.0 indicates alignment to the full span
  587. * away from the origin. An alignment of 0.5 would be the
  588. * center of the view.
  589. */
  590. public float getAlignment(int axis) {
  591. switch (axis) {
  592. case Y_AXIS:
  593. float a = 0.5f;
  594. if (getViewCount() != 0) {
  595. int paragraphSpan = (int) getPreferredSpan(View.Y_AXIS);
  596. View v = getView(0);
  597. int rowSpan = (int) v.getPreferredSpan(View.Y_AXIS);
  598. a = (paragraphSpan != 0) ? ((float)(rowSpan / 2)) / paragraphSpan : 0;
  599. }
  600. return a;
  601. case X_AXIS:
  602. return 0.5f;
  603. default:
  604. throw new IllegalArgumentException("Invalid axis: " + axis);
  605. }
  606. }
  607. /**
  608. * Breaks this view on the given axis at the given length.
  609. * <p>
  610. * <code>ParagraphView</code> instances are breakable
  611. * along the <code>Y_AXIS</code> only, and only if
  612. * <code>len</code> is after the first line.
  613. *
  614. * @param axis may be either <code>View.X_AXIS</code>
  615. * or <code>View.Y_AXIS</code>
  616. * @param len specifies where a potential break is desired
  617. * along the given axis >= 0
  618. * @param a the current allocation of the view
  619. * @return the fragment of the view that represents the
  620. * given span, if the view can be broken; if the view
  621. * doesn't support breaking behavior, the view itself is
  622. * returned
  623. * @see View#breakView
  624. */
  625. public View breakView(int axis, float len, Shape a) {
  626. if(axis == View.Y_AXIS) {
  627. if(a != null) {
  628. Rectangle alloc = a.getBounds();
  629. setSize(alloc.width, alloc.height);
  630. }
  631. // Determine what row to break on.
  632. // PENDING(prinz) add break support
  633. return this;
  634. }
  635. return this;
  636. }
  637. /**
  638. * Gets the break weight for a given location.
  639. * <p>
  640. * <code>ParagraphView</code> instances are breakable
  641. * along the <code>Y_AXIS</code> only, and only if
  642. * <code>len</code> is after the first row. If the length
  643. * is less than one row, a value of <code>BadBreakWeight</code>
  644. * is returned.
  645. *
  646. * @param axis may be either <code>View.X_AXIS</code>
  647. * or <code>View.Y_AXIS</code>
  648. * @param len specifies where a potential break is desired >= 0
  649. * @return a value indicating the attractiveness of breaking here;
  650. * either <code>GoodBreakWeight</code> or <code>BadBreakWeight</code>
  651. * @see View#getBreakWeight
  652. */
  653. public int getBreakWeight(int axis, float len) {
  654. if(axis == View.Y_AXIS) {
  655. // PENDING(prinz) make this return a reasonable value
  656. // when paragraph breaking support is re-implemented.
  657. // If less than one row, bad weight value should be
  658. // returned.
  659. //return GoodBreakWeight;
  660. return BadBreakWeight;
  661. }
  662. return BadBreakWeight;
  663. }
  664. /**
  665. * Gives notification from the document that attributes were changed
  666. * in a location that this view is responsible for.
  667. *
  668. * @param changes the change information from the
  669. * associated document
  670. * @param a the current allocation of the view
  671. * @param f the factory to use to rebuild if the view has children
  672. * @see View#changedUpdate
  673. */
  674. public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
  675. // update any property settings stored, and layout should be
  676. // recomputed
  677. setPropertiesFromAttributes();
  678. layoutChanged(X_AXIS);
  679. layoutChanged(Y_AXIS);
  680. super.changedUpdate(changes, a, f);
  681. }
  682. // --- variables -----------------------------------------------
  683. private int justification;
  684. private float lineSpacing;
  685. /** Indentation for the first line, from the left inset. */
  686. protected int firstLineIndent = 0;
  687. /**
  688. * Used by the TabExpander functionality to determine
  689. * where to base the tab calculations. This is basically
  690. * the location of the left side of the paragraph.
  691. */
  692. private int tabBase;
  693. /**
  694. * Used to create an i18n-based layout strategy
  695. */
  696. static Class i18nStrategy;
  697. /** Used for searching for a tab. */
  698. static char[] tabChars;
  699. /** Used for searching for a tab or decimal character. */
  700. static char[] tabDecimalChars;
  701. static {
  702. tabChars = new char[1];
  703. tabChars[0] = '\t';
  704. tabDecimalChars = new char[2];
  705. tabDecimalChars[0] = '\t';
  706. tabDecimalChars[1] = '.';
  707. }
  708. /**
  709. * Internally created view that has the purpose of holding
  710. * the views that represent the children of the paragraph
  711. * that have been arranged in rows.
  712. */
  713. class Row extends BoxView {
  714. Row(Element elem) {
  715. super(elem, View.X_AXIS);
  716. }
  717. /**
  718. * This is reimplemented to do nothing since the
  719. * paragraph fills in the row with its needed
  720. * children.
  721. */
  722. protected void loadChildren(ViewFactory f) {
  723. }
  724. /**
  725. * Fetches the attributes to use when rendering. This view
  726. * isn't directly responsible for an element so it returns
  727. * the outer classes attributes.
  728. */
  729. public AttributeSet getAttributes() {
  730. View p = getParent();
  731. return (p != null) ? p.getAttributes() : null;
  732. }
  733. public float getAlignment(int axis) {
  734. if (axis == View.X_AXIS) {
  735. switch (justification) {
  736. case StyleConstants.ALIGN_LEFT:
  737. return 0;
  738. case StyleConstants.ALIGN_RIGHT:
  739. return 1;
  740. case StyleConstants.ALIGN_CENTER:
  741. case StyleConstants.ALIGN_JUSTIFIED:
  742. return 0.5f;
  743. }
  744. }
  745. return super.getAlignment(axis);
  746. }
  747. /**
  748. * Provides a mapping from the document model coordinate space
  749. * to the coordinate space of the view mapped to it. This is
  750. * implemented to let the superclass find the position along
  751. * the major axis and the allocation of the row is used
  752. * along the minor axis, so that even though the children
  753. * are different heights they all get the same caret height.
  754. *
  755. * @param pos the position to convert
  756. * @param a the allocated region to render into
  757. * @return the bounding box of the given position
  758. * @exception BadLocationException if the given position does not represent a
  759. * valid location in the associated document
  760. * @see View#modelToView
  761. */
  762. public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
  763. Rectangle r = a.getBounds();
  764. View v = getViewAtPosition(pos, r);
  765. if ((v != null) && (!v.getElement().isLeaf())) {
  766. // Don't adjust the height if the view represents a branch.
  767. return super.modelToView(pos, a, b);
  768. }
  769. r = a.getBounds();
  770. int height = r.height;
  771. int y = r.y;
  772. Shape loc = super.modelToView(pos, a, b);
  773. r = loc.getBounds();
  774. r.height = height;
  775. r.y = y;
  776. return r;
  777. }
  778. /**
  779. * Range represented by a row in the paragraph is only
  780. * a subset of the total range of the paragraph element.
  781. * @see View#getRange
  782. */
  783. public int getStartOffset() {
  784. int offs = Integer.MAX_VALUE;
  785. int n = getViewCount();
  786. for (int i = 0; i < n; i++) {
  787. View v = getView(i);
  788. offs = Math.min(offs, v.getStartOffset());
  789. }
  790. return offs;
  791. }
  792. public int getEndOffset() {
  793. int offs = 0;
  794. int n = getViewCount();
  795. for (int i = 0; i < n; i++) {
  796. View v = getView(i);
  797. offs = Math.max(offs, v.getEndOffset());
  798. }
  799. return offs;
  800. }
  801. /**
  802. * Perform layout for the minor axis of the box (i.e. the
  803. * axis orthoginal to the axis that it represents). The results
  804. * of the layout should be placed in the given arrays which represent
  805. * the allocations to the children along the minor axis.
  806. * <p>
  807. * This is implemented to do a baseline layout of the children
  808. * by calling BoxView.baselineLayout.
  809. *
  810. * @param targetSpan the total span given to the view, which
  811. * whould be used to layout the children.
  812. * @param axis the axis being layed out.
  813. * @param offsets the offsets from the origin of the view for
  814. * each of the child views. This is a return value and is
  815. * filled in by the implementation of this method.
  816. * @param spans the span of each child view. This is a return
  817. * value and is filled in by the implementation of this method.
  818. * @return the offset and span for each child view in the
  819. * offsets and spans parameters
  820. */
  821. protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
  822. baselineLayout(targetSpan, axis, offsets, spans);
  823. }
  824. protected SizeRequirements calculateMinorAxisRequirements(int axis,
  825. SizeRequirements r) {
  826. return baselineRequirements(axis, r);
  827. }
  828. /**
  829. * Fetches the child view index representing the given position in
  830. * the model.
  831. *
  832. * @param pos the position >= 0
  833. * @return index of the view representing the given position, or
  834. * -1 if no view represents that position
  835. */
  836. protected int getViewIndexAtPosition(int pos) {
  837. // This is expensive, but are views are not necessarily layed
  838. // out in model order.
  839. if(pos < getStartOffset() || pos >= getEndOffset())
  840. return -1;
  841. for(int counter = getViewCount() - 1; counter >= 0; counter--) {
  842. View v = getView(counter);
  843. if(pos >= v.getStartOffset() &&
  844. pos < v.getEndOffset()) {
  845. return counter;
  846. }
  847. }
  848. return -1;
  849. }
  850. /**
  851. * Gets the left inset.
  852. *
  853. * @return the inset
  854. */
  855. protected short getLeftInset() {
  856. View parentView;
  857. int adjustment = 0;
  858. if ((parentView = getParent()) != null) { //use firstLineIdent for the first row
  859. if (this == parentView.getView(0)) {
  860. adjustment = firstLineIndent;
  861. }
  862. }
  863. return (short)(super.getLeftInset() + adjustment);
  864. }
  865. protected short getBottomInset() {
  866. return (short)(super.getBottomInset() +
  867. ((minorRequest != null) ? minorRequest.preferred : 0) *
  868. lineSpacing);
  869. }
  870. }
  871. }