1. /*
  2. * @(#)ParagraphView.java 1.67 01/11/29
  3. *
  4. * Copyright 2002 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.util.Vector;
  9. import java.util.Properties;
  10. import java.awt.*;
  11. import javax.swing.event.*;
  12. import javax.swing.SizeRequirements;
  13. import javax.swing.SwingConstants;
  14. /**
  15. * View of a simple line-wrapping paragraph that supports
  16. * multiple fonts, colors, components, icons, etc. It is
  17. * basically a vertical box with a margin around it. The
  18. * contents of the box are a bunch of rows which are special
  19. * horizontal boxes. This view creates a collection of
  20. * views that represent the child elements of the paragraph
  21. * element. Each of these views are placed into a row
  22. * directly if they will fit, otherwise the <code>breakView</code>
  23. * method is called to try and carve the view into pieces
  24. * that fit.
  25. *
  26. * @author Timothy Prinzing
  27. * @author Scott Violet
  28. * @version 1.67 11/29/01
  29. * @see View
  30. */
  31. public class ParagraphView extends BoxView implements TabExpander {
  32. /**
  33. * Constructs a ParagraphView for the given element.
  34. *
  35. * @param elem the element that this view is responsible for
  36. */
  37. public ParagraphView(Element elem) {
  38. super(elem, View.Y_AXIS);
  39. layoutSpan = -1;
  40. setPropertiesFromAttributes();
  41. }
  42. /**
  43. * Set the type of justification.
  44. */
  45. protected void setJustification(int j) {
  46. justification = j;
  47. }
  48. /**
  49. * Set the line spacing.
  50. *
  51. * @param ls the value in points
  52. */
  53. protected void setLineSpacing(float ls) {
  54. lineSpacing = ls;
  55. }
  56. /**
  57. * Set the indent on the first line
  58. *
  59. * @param ls the value in points
  60. */
  61. protected void setFirstLineIndent(float fi) {
  62. firstLineIndent = (int) fi;
  63. }
  64. protected void setPropertiesFromAttributes() {
  65. AttributeSet attr = getAttributes();
  66. if (attr != null) {
  67. setParagraphInsets(attr);
  68. setJustification(StyleConstants.getAlignment(attr));
  69. lineSpacing = StyleConstants.getLineSpacing(attr);
  70. firstLineIndent = (int)StyleConstants.getFirstLineIndent(attr);
  71. }
  72. }
  73. /**
  74. * The child views of the paragraph are rows which
  75. * have been used to arrange pieces of the Views that
  76. * represent the child elements. This is the number
  77. * of views that have been tiled in two dimensions,
  78. * and should be equivalent to the number of child elements
  79. * to the element this view is responsible for.
  80. */
  81. protected int getLayoutViewCount() {
  82. return layoutPool.size();
  83. }
  84. /**
  85. * The child views of the paragraph are rows which
  86. * have been used to arrange pieces of the Views that
  87. * represent the child elements. This methods returns
  88. * the view responsible for the child element index
  89. * (prior to breaking). These are the Views that were
  90. * produced from a factory (to represent the child
  91. * elements) and used for layout.
  92. */
  93. protected View getLayoutView(int index) {
  94. return (View) layoutPool.elementAt(index);
  95. }
  96. /**
  97. * Loads all of the children to initialize the view.
  98. * This is called by the <code>setParent</code> method.
  99. * This is reimplemented to not load any children directly
  100. * (as they are created in the process of formatting).
  101. * This does create views to represent the child elements,
  102. * but they are placed into a pool that is used in the
  103. * process of formatting.
  104. *
  105. * @param f the view factory
  106. */
  107. protected void loadChildren(ViewFactory f) {
  108. layoutPool = new Vector();
  109. Element e = getElement();
  110. int n = e.getElementCount();
  111. for (int i = 0; i < n; i++) {
  112. View v = f.create(e.getElement(i));
  113. v.setParent(this);
  114. layoutPool.addElement(v);
  115. }
  116. }
  117. /**
  118. * Fetches the child view that represents the given position in
  119. * the model. This is implemented to walk through the children
  120. * looking for a range that contains the given position. In this
  121. * view the children do not have a one to one mapping with the
  122. * child elements (i.e. the children are actually rows that
  123. * represent a portion of the element this view represents).
  124. *
  125. * @param pos the search position >= 0
  126. * @param a the allocation to the box on entry, and the
  127. * allocation of the view containing the position on exit
  128. * @returns the view representing the given position, or
  129. * null if there isn't one
  130. */
  131. protected View getViewAtPosition(int pos, Rectangle a) {
  132. int n = getViewCount();
  133. for (int i = 0; i < n; i++) {
  134. View v = getView(i);
  135. int p0 = v.getStartOffset();
  136. int p1 = v.getEndOffset();
  137. if ((pos >= p0) && (pos < p1)) {
  138. // it's in this view.
  139. if (a != null) {
  140. childAllocation(i, a);
  141. }
  142. return v;
  143. }
  144. }
  145. if (pos == getEndOffset()) {
  146. // PENDING(bcb): This will probably want to choose the first
  147. // if right to left.
  148. View v = getView(n - 1);
  149. if (a != null) {
  150. this.childAllocation(n - 1, a);
  151. }
  152. return v;
  153. }
  154. return null;
  155. }
  156. /**
  157. * Fetches the child view index representing the given position in
  158. * the model.
  159. *
  160. * @param pos the position >= 0
  161. * @returns index of the view representing the given position, or
  162. * -1 if no view represents that position
  163. */
  164. protected int getViewIndexAtPosition(int pos) {
  165. // This is expensive, but are views are not necessarily layed
  166. // out in model order.
  167. if(pos < getStartOffset() || pos >= getEndOffset())
  168. return -1;
  169. for(int counter = getViewCount() - 1; counter >= 0; counter--) {
  170. View v = getView(counter);
  171. if(pos >= v.getStartOffset() &&
  172. pos < v.getEndOffset()) {
  173. return counter;
  174. }
  175. }
  176. return -1;
  177. }
  178. /**
  179. * Lays out the children. If the layout span has changed,
  180. * the rows are rebuilt. The superclass functionality
  181. * is called after checking and possibly rebuilding the
  182. * rows. If the height has changed, the
  183. * <code>preferenceChanged</code> method is called
  184. * on the parent since the vertical preference is
  185. * rigid.
  186. *
  187. * @param width the width to lay out against >= 0. This is
  188. * the width inside of the inset area.
  189. * @param height the height to lay out against >= 0 (not used
  190. * by paragraph, but used by the superclass). This
  191. * is the height inside of the inset area.
  192. */
  193. protected void layout(int width, int height) {
  194. if (layoutSpan != width) {
  195. int oldHeight = height;
  196. rebuildRows(width);
  197. int newHeight = (int) getPreferredSpan(Y_AXIS);
  198. if (oldHeight != newHeight) {
  199. View p = getParent();
  200. p.preferenceChanged(this, false, true);
  201. }
  202. }
  203. // do normal box layout
  204. super.layout(width, height);
  205. }
  206. /**
  207. * Does a a full layout on this View. This causes all of
  208. * the rows (child views) to be rebuilt to match the given
  209. * span of the given allocation.
  210. *
  211. * @param span the length to layout against.
  212. */
  213. void rebuildRows(int span) {
  214. layoutSpan = span;
  215. int p0 = getStartOffset();
  216. int p1 = getEndOffset();
  217. removeAll();
  218. // Removing the rows may leave some views in the layout pool
  219. // disconnected from the view tree. Rather than trying to
  220. // figure out which views these are, we simply reparent all of
  221. // the views in the pool.
  222. int n = layoutPool.size();
  223. for( int i=0; i<n; i++ ) {
  224. View v = (View)layoutPool.elementAt(i);
  225. v.setParent(this);
  226. }
  227. boolean firstRow = true;
  228. while(p0 < p1) {
  229. int old = p0;
  230. // PENDING(prinz) The old rows should be reused and
  231. // new ones created only if needed... and discarded
  232. // only if not needed.
  233. Row row = new Row(getElement());
  234. if(firstRow) {
  235. // Give it at least 5 pixels.
  236. row.setInsets((short)0, (short)Math.min(span - 5,
  237. firstLineIndent),
  238. (short)0, (short)0);
  239. firstRow = false;
  240. }
  241. append(row);
  242. // layout the row to the current span
  243. layoutRow(row, p0);
  244. p0 = row.getEndOffset();
  245. if (p0 <= old) {
  246. throw new StateInvariantError("infinite loop in formatting");
  247. }
  248. }
  249. }
  250. /**
  251. * Creates a row of views that will fit within the
  252. * current layout span. The rows occupy the area
  253. * from the left inset to the right inset.
  254. *
  255. * @param row the row to fill in with views. This is assumed
  256. * to be empty on entry.
  257. * @param pos The current position in the children of
  258. * this views element from which to start.
  259. */
  260. void layoutRow(Row row, int pos) {
  261. int x = tabBase + getLeftInset();
  262. int spanLeft = layoutSpan;
  263. int end = getEndOffset();
  264. // Indentation.
  265. int preX = x;
  266. x += row.getLeftInset();
  267. spanLeft -= (x - preX);
  268. int availableSpan = spanLeft;
  269. preX = x;
  270. boolean forcedBreak = false;
  271. while (pos < end && spanLeft > 0) {
  272. View v = createView(pos);
  273. int chunkSpan;
  274. if (v instanceof TabableView) {
  275. chunkSpan = (int) ((TabableView)v).getTabbedSpan(x, this);
  276. } else {
  277. chunkSpan = (int) v.getPreferredSpan(View.X_AXIS);
  278. }
  279. // If a forced break is necessary, break
  280. if (v.getBreakWeight(View.X_AXIS, pos, spanLeft) >= ForcedBreakWeight) {
  281. int n = row.getViewCount();
  282. if (n > 0) {
  283. /* If this is a forced break and it's not the only view
  284. * the view should be replaced with a call to breakView.
  285. * If it's it only view, it should be used directly. In
  286. * either case no more children should be added beyond this
  287. * view.
  288. */
  289. v = v.breakView(X_AXIS, pos, x, spanLeft);
  290. if (v != null) {
  291. if (v instanceof TabableView) {
  292. chunkSpan = (int) ((TabableView)v).getTabbedSpan(x, this);
  293. } else {
  294. chunkSpan = (int) v.getPreferredSpan(View.X_AXIS);
  295. }
  296. } else {
  297. chunkSpan = 0;
  298. }
  299. }
  300. forcedBreak = true;
  301. }
  302. spanLeft -= chunkSpan;
  303. x += chunkSpan;
  304. if (v != null) {
  305. row.append(v);
  306. pos = v.getEndOffset();
  307. }
  308. if (forcedBreak) {
  309. break;
  310. }
  311. }
  312. if (spanLeft < 0) {
  313. // This row is too long and needs to be adjusted.
  314. adjustRow(row, availableSpan, preX);
  315. } else if (row.getViewCount() == 0) {
  316. // Impossible spec... put in whatever is left.
  317. View v = createView(pos);
  318. row.append(v);
  319. }
  320. // Adjust for line spacing
  321. if(lineSpacing > 1) {
  322. float height = row.getPreferredSpan(View.Y_AXIS);
  323. float addition = (height * lineSpacing) - height;
  324. if(addition > 0) {
  325. row.setInsets(row.getTopInset(), row.getLeftInset(),
  326. (short) addition, row.getRightInset());
  327. }
  328. }
  329. }
  330. /**
  331. * Adjusts the given row if possible to fit within the
  332. * layout span. By default this will try to find the
  333. * highest break weight possible nearest the end of
  334. * the row. If a forced break is encountered, the
  335. * break will be positioned there.
  336. *
  337. * @param r the row to adjust to the current layout
  338. * span.
  339. * @param desiredSpan the current layout span >= 0
  340. * @param x the location r starts at.
  341. */
  342. protected void adjustRow(Row r, int desiredSpan, int x) {
  343. int n = r.getViewCount();
  344. int span = 0;
  345. int bestWeight = BadBreakWeight;
  346. int bestSpan = 0;
  347. int bestIndex = -1;
  348. int bestOffset = 0;
  349. View v;
  350. for (int i = 0; i < n; i++) {
  351. v = r.getView(i);
  352. int spanLeft = desiredSpan - span;
  353. int w = v.getBreakWeight(X_AXIS, x + span, spanLeft);
  354. if (w >= bestWeight) {
  355. bestWeight = w;
  356. bestIndex = i;
  357. bestSpan = span;
  358. if (w >= ForcedBreakWeight) {
  359. // it's a forced break, so there is
  360. // no point in searching further.
  361. break;
  362. }
  363. }
  364. span += v.getPreferredSpan(X_AXIS);
  365. }
  366. if (bestIndex < 0) {
  367. // there is nothing that can be broken, leave
  368. // it in it's current state.
  369. return;
  370. }
  371. // Break the best candidate view, and patch up the row.
  372. int spanLeft = desiredSpan - bestSpan;
  373. v = r.getView(bestIndex);
  374. v = v.breakView(X_AXIS, v.getStartOffset(), x + bestSpan, spanLeft);
  375. View[] va = new View[1];
  376. va[0] = v;
  377. r.replace(bestIndex, n - bestIndex, va);
  378. // The views removed from the row now live in the layout pool with a
  379. // null parent. These must be reparented. Note: we could remember
  380. // what is being replaced and then reparent exactly those, but its
  381. // probably faster to just search the layout pool.
  382. int poolSize = layoutPool.size();
  383. for( int i=0; i<poolSize; i++ ) {
  384. v = (View)layoutPool.elementAt(i);
  385. if( v.getParent() == null )
  386. v.setParent(this);
  387. }
  388. }
  389. /**
  390. * Creates a unidirectional view that can be used to represent the
  391. * current chunk. This can be either an entire view from the
  392. * layout pool, or a fragment there of.
  393. */
  394. View createView(int startOffset) {
  395. // Get the child view that contains the given starting position
  396. int childIndex = getElement().getElementIndex(startOffset);
  397. View v = (View) layoutPool.elementAt(childIndex);
  398. int endOffset = v.getEndOffset();
  399. // REMIND (bcb) handle case of not an abstract document.
  400. AbstractDocument d = (AbstractDocument)getDocument();
  401. if(d.getProperty(AbstractDocument.I18NProperty).equals(Boolean.TRUE)) {
  402. Element bidiRoot = d.getBidiRootElement();
  403. if( bidiRoot.getElementCount() > 1 ) {
  404. int bidiIndex = bidiRoot.getElementIndex( startOffset );
  405. Element bidiElem = bidiRoot.getElement( bidiIndex );
  406. endOffset = Math.min( bidiElem.getEndOffset(), endOffset );
  407. }
  408. }
  409. if (startOffset==v.getStartOffset() && endOffset==v.getEndOffset()) {
  410. // return the entire view
  411. return v;
  412. }
  413. // return a unidirectional fragment.
  414. v = v.createFragment(startOffset, endOffset);
  415. return v;
  416. }
  417. // --- TabExpander methods ------------------------------------------
  418. /**
  419. * Returns the next tab stop position given a reference position.
  420. * This view implements the tab coordinate system, and calls
  421. * <code>getTabbedSpan</code> on the logical children in the process
  422. * of layout to determine the desired span of the children. The
  423. * logical children can delegate their tab expansion upward to
  424. * the paragraph which knows how to expand tabs.
  425. * <code>LabelView</code> is an example of a view that delegates
  426. * its tab expansion needs upward to the paragraph.
  427. * <p>
  428. * This is implemented to try and locate a <code>TabSet</code>
  429. * in the paragraph element's attribute set. If one can be
  430. * found, its settings will be used, otherwise a default expansion
  431. * will be provided. The base location for for tab expansion
  432. * is the left inset from the paragraphs most recent allocation
  433. * (which is what the layout of the children is based upon).
  434. *
  435. * @param x the X reference position
  436. * @param tabOffset the position within the text stream
  437. * that the tab occurred at >= 0.
  438. * @return the trailing end of the tab expansion >= 0
  439. * @see TabSet
  440. * @see TabStop
  441. * @see LabelView
  442. */
  443. public float nextTabStop(float x, int tabOffset) {
  444. // If the text isn't left justified, offset by 10 pixels!
  445. if(justification != StyleConstants.ALIGN_LEFT)
  446. return x + 10.0f;
  447. x -= tabBase;
  448. TabSet tabs = getTabSet();
  449. if(tabs == null) {
  450. // a tab every 72 pixels.
  451. return (float)(tabBase + (((int)x / 72 + 1) * 72));
  452. }
  453. TabStop tab = tabs.getTabAfter(x + .01f);
  454. if(tab == null) {
  455. // no tab, do a default of 5 pixels.
  456. // Should this cause a wrapping of the line?
  457. return tabBase + x + 5.0f;
  458. }
  459. int alignment = tab.getAlignment();
  460. int offset;
  461. switch(alignment) {
  462. default:
  463. case TabStop.ALIGN_LEFT:
  464. // Simple case, left tab.
  465. return tabBase + tab.getPosition();
  466. case TabStop.ALIGN_BAR:
  467. // PENDING: what does this mean?
  468. return tabBase + tab.getPosition();
  469. case TabStop.ALIGN_RIGHT:
  470. case TabStop.ALIGN_CENTER:
  471. offset = findOffsetToCharactersInString(tabChars,
  472. tabOffset + 1);
  473. break;
  474. case TabStop.ALIGN_DECIMAL:
  475. offset = findOffsetToCharactersInString(tabDecimalChars,
  476. tabOffset + 1);
  477. break;
  478. }
  479. if (offset == -1) {
  480. offset = getEndOffset();
  481. }
  482. float charsSize = getPartialSize(tabOffset + 1, offset);
  483. switch(alignment) {
  484. case TabStop.ALIGN_RIGHT:
  485. case TabStop.ALIGN_DECIMAL:
  486. // right and decimal are treated the same way, the new
  487. // position will be the location of the tab less the
  488. // partialSize.
  489. return tabBase + Math.max(x, tab.getPosition() - charsSize);
  490. case TabStop.ALIGN_CENTER:
  491. // Similar to right, but half the partialSize.
  492. return tabBase + Math.max(x, tab.getPosition() - charsSize / 2.0f);
  493. }
  494. // will never get here!
  495. return x;
  496. }
  497. /**
  498. * Gets the Tabset to be used in calculating tabs.
  499. *
  500. * @return the TabSet
  501. */
  502. protected TabSet getTabSet() {
  503. return StyleConstants.getTabSet(getElement().getAttributes());
  504. }
  505. /**
  506. * Returns the size used by the views between <code>startOffset</code>
  507. * and <code>endOffset</code>. This uses getPartialView to calculate the
  508. * size if the child view implements the TabableView interface. If a
  509. * size is needed and a View does not implement the TabableView
  510. * interface, the preferredSpan will be used.
  511. *
  512. * @param startOffset the starting document offset >= 0
  513. * @param endOffset the ending document offset >= startOffset
  514. * @return the size >= 0
  515. */
  516. protected float getPartialSize(int startOffset, int endOffset) {
  517. float size = 0.0f;
  518. int viewIndex;
  519. int numViews = getViewCount();
  520. View view;
  521. int viewEnd;
  522. int tempEnd;
  523. // Have to search layoutPool!
  524. // PENDING: when ParagraphView supports breaking location
  525. // into layoutPool will have to change!
  526. viewIndex = getElement().getElementIndex(startOffset);
  527. numViews = layoutPool.size();
  528. while(startOffset < endOffset && viewIndex < numViews) {
  529. view = (View) layoutPool.elementAt(viewIndex++);
  530. viewEnd = view.getEndOffset();
  531. tempEnd = Math.min(endOffset, viewEnd);
  532. if(view instanceof TabableView)
  533. size += ((TabableView)view).getPartialSpan(startOffset, tempEnd);
  534. else if(startOffset == view.getStartOffset() &&
  535. tempEnd == view.getEndOffset())
  536. size += view.getPreferredSpan(View.X_AXIS);
  537. else
  538. // PENDING: should we handle this better?
  539. return 0.0f;
  540. startOffset = viewEnd;
  541. }
  542. return size;
  543. }
  544. /**
  545. * Finds the next character in the document with a character in
  546. * <code>string</code>, starting at offset <code>start</code>. If
  547. * there are no characters found, -1 will be returned.
  548. *
  549. * @param string the string of characters
  550. * @param start where to start in the model >= 0
  551. * @return the document offset or -1
  552. */
  553. protected int findOffsetToCharactersInString(char[] string,
  554. int start) {
  555. int stringLength = string.length;
  556. int end = getEndOffset();
  557. Segment seg = new Segment();
  558. try {
  559. getDocument().getText(start, end - start, seg);
  560. } catch (BadLocationException ble) {
  561. return -1;
  562. }
  563. for(int counter = seg.offset, maxCounter = seg.offset + seg.count;
  564. counter < maxCounter; counter++) {
  565. char currentChar = seg.array[counter];
  566. for(int subCounter = 0; subCounter < stringLength;
  567. subCounter++) {
  568. if(currentChar == string[subCounter])
  569. return counter - seg.offset + start;
  570. }
  571. }
  572. // No match.
  573. return -1;
  574. }
  575. /**
  576. * @return where tabs are calculated from.
  577. */
  578. protected float getTabBase() {
  579. return (float)tabBase;
  580. }
  581. protected boolean flipEastAndWestAtEnds(int position,
  582. Position.Bias bias) {
  583. Document doc = getDocument();
  584. if(doc instanceof AbstractDocument &&
  585. !((AbstractDocument)doc).isLeftToRight(getStartOffset(),
  586. getStartOffset() + 1)) {
  587. return true;
  588. }
  589. return false;
  590. }
  591. // ---- View methods ----------------------------------------------------
  592. /**
  593. * Renders using the given rendering surface and area on that
  594. * surface. This is implemented to delgate to the superclass
  595. * after stashing the base coordinate for tab calculations.
  596. *
  597. * @param g the rendering surface to use
  598. * @param a the allocated region to render into
  599. * @see View#paint
  600. */
  601. public void paint(Graphics g, Shape a) {
  602. Rectangle alloc = a.getBounds();
  603. tabBase = alloc.x;
  604. super.paint(g, a);
  605. }
  606. protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) {
  607. if (r == null) {
  608. r = new SizeRequirements();
  609. }
  610. float pref = 0;
  611. int n = layoutPool.size();
  612. for (int i = 0; i < n; i++) {
  613. View v = (View) layoutPool.elementAt(i);
  614. pref += v.getPreferredSpan(axis);
  615. }
  616. float insets = (axis == X_AXIS) ? getLeftInset() + getRightInset() :
  617. getTopInset() + getBottomInset();
  618. r.minimum = ((int) insets) + 5;
  619. r.preferred = Math.max(r.minimum, (int) pref);
  620. r.maximum = Short.MAX_VALUE;
  621. r.alignment = 0.5f;
  622. return r;
  623. }
  624. /**
  625. * Determines the desired alignment for this view along an
  626. * axis. This is implemented to give the alignment to the
  627. * center of the first row along the y axis, and the default
  628. * along the x axis.
  629. *
  630. * @param axis may be either View.X_AXIS or View.Y_AXIS
  631. * @returns the desired alignment. This should be a value
  632. * between 0.0 and 1.0 inclusive, where 0 indicates alignment at the
  633. * origin and 1.0 indicates alignment to the full span
  634. * away from the origin. An alignment of 0.5 would be the
  635. * center of the view.
  636. */
  637. public float getAlignment(int axis) {
  638. switch (axis) {
  639. case Y_AXIS:
  640. float a = 0.5f;
  641. if (getViewCount() != 0) {
  642. int paragraphSpan = (int) getPreferredSpan(View.Y_AXIS);
  643. View v = getView(0);
  644. int rowSpan = (int) v.getPreferredSpan(View.Y_AXIS);
  645. a = (paragraphSpan != 0) ? ((float)(rowSpan / 2)) / paragraphSpan : 0;
  646. }
  647. return a;
  648. case X_AXIS:
  649. return 0.5f;
  650. default:
  651. throw new IllegalArgumentException("Invalid axis: " + axis);
  652. }
  653. }
  654. /**
  655. * Breaks this view on the given axis at the given length.<p>
  656. * ParagraphView instances are breakable along the Y_AXIS only, and only if
  657. * <code>len</code> is after the first line.
  658. *
  659. * @param axis may be either View.X_AXIS or View.Y_AXIS
  660. * @param len specifies where a potential break is desired
  661. * along the given axis >= 0
  662. * @param a the current allocation of the view
  663. * @return the fragment of the view that represents the
  664. * given span, if the view can be broken. If the view
  665. * doesn't support breaking behavior, the view itself is
  666. * returned.
  667. * @see View#breakView
  668. */
  669. public View breakView(int axis, float len, Shape a) {
  670. if(axis == View.Y_AXIS) {
  671. if(a != null) {
  672. Rectangle alloc = a.getBounds();
  673. setSize(alloc.width, alloc.height);
  674. }
  675. // Determine what row to break on.
  676. // PENDING(prinz) add break support
  677. return this;
  678. }
  679. return this;
  680. }
  681. /**
  682. * Gets the break weight for a given location.
  683. * ParagraphView instances are breakable along the Y_AXIS only, and
  684. * only if <code>len</code> is after the first row. If the length
  685. * is less than one row, a value of BadBreakWeight is returned.
  686. *
  687. * @param axis may be either View.X_AXIS or View.Y_AXIS
  688. * @param len specifies where a potential break is desired >= 0
  689. * @return a value indicating the attractiveness of breaking here
  690. * @see View#getBreakWeight
  691. */
  692. public int getBreakWeight(int axis, float len) {
  693. if(axis == View.Y_AXIS) {
  694. // PENDING(prinz) make this return a reasonable value
  695. // when paragraph breaking support is re-implemented.
  696. // If less than one row, bad weight value should be
  697. // returned.
  698. //return GoodBreakWeight;
  699. return BadBreakWeight;
  700. }
  701. return BadBreakWeight;
  702. }
  703. /**
  704. * Gives notification that something was inserted into the document
  705. * in a location that this view is responsible for.
  706. *
  707. * @param changes the change information from the associated document
  708. * @param a the current allocation of the view
  709. * @param f the factory to use to rebuild if the view has children
  710. * @see View#insertUpdate
  711. */
  712. public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
  713. // update the pool of logical children
  714. Element elem = getElement();
  715. DocumentEvent.ElementChange ec = changes.getChange(elem);
  716. if (ec != null) {
  717. // the structure of this element changed.
  718. updateLogicalChildren(ec, f);
  719. }
  720. // find and forward if there is anything there to
  721. // forward to. If children were removed then there was
  722. // a replacement of the removal range and there is no
  723. // need to forward.
  724. if (ec != null && ec.getChildrenAdded().length > 0) {
  725. int index = ec.getIndex();
  726. int pos = changes.getOffset();
  727. if (index > 0) {
  728. Element child = elem.getElement(index - 1);
  729. if (child.getEndOffset() >= pos) {
  730. View v = (View)layoutPool.elementAt(index - 1);
  731. v.insertUpdate(changes, null, f);
  732. }
  733. }
  734. int endIndex = index + ec.getChildrenAdded().length;
  735. if (endIndex < layoutPool.size()) {
  736. Element child = elem.getElement(endIndex);
  737. int start = child.getStartOffset();
  738. if (start >= pos && start <= (pos + changes.getLength())) {
  739. View v = (View)layoutPool.elementAt(endIndex);
  740. v.insertUpdate(changes, null, f);
  741. }
  742. }
  743. }
  744. //REMIND(bcb) It is possible for an event have no added children,
  745. //a removed child and a change to an existing child. To see, this
  746. //do the following. Bring up Stylepad. Empty its contents. Select
  747. //a different font. Type a line until it wraps then hit return.
  748. //Someone should code review this change.
  749. else/* if (ec == null || (ec.getChildrenRemoved().length == 0)) */{
  750. int pos = changes.getOffset();
  751. int index = elem.getElementIndex(pos);
  752. View v = (View) layoutPool.elementAt(index);
  753. v.insertUpdate(changes, null, f);
  754. if (index > 0 && v.getStartOffset() == pos) {
  755. v = (View)layoutPool.elementAt(index - 1);
  756. v.insertUpdate(changes, null, f);
  757. }
  758. }
  759. // force layout, should do something more intelligent about
  760. // incurring damage and triggering a new layout. This is just
  761. // about as brute force as it can get.
  762. layoutSpan = Integer.MAX_VALUE;
  763. preferenceChanged(null, true, true);
  764. Rectangle alloc = getInsideAllocation(a);
  765. if (alloc != null) {
  766. layout(alloc.width, alloc.height);
  767. Component host = getContainer();
  768. host.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
  769. }
  770. }
  771. /**
  772. * Update the logical children to reflect changes made
  773. * to the element this view is responsible. This updates
  774. * the pool of views used for layout (ie. the views
  775. * representing the child elements of the element this
  776. * view is responsible for). This is called by the
  777. * <code>insertUpdate, removeUpdate, and changeUpdate</code>
  778. * methods.
  779. */
  780. void updateLogicalChildren(DocumentEvent.ElementChange ec, ViewFactory f) {
  781. int index = ec.getIndex();
  782. Element[] removedElems = ec.getChildrenRemoved();
  783. for (int i = 0; i < removedElems.length; i++) {
  784. View v = (View) layoutPool.elementAt(index);
  785. v.setParent(null);
  786. layoutPool.removeElementAt(index);
  787. }
  788. Element[] addedElems = ec.getChildrenAdded();
  789. for (int i = 0; i < addedElems.length; i++) {
  790. View v = f.create(addedElems[i]);
  791. v.setParent(this);
  792. layoutPool.insertElementAt(v, index + i);
  793. }
  794. }
  795. /**
  796. * Gives notification that something was removed from the document
  797. * in a location that this view is responsible for.
  798. *
  799. * @param changes the change information from the associated document
  800. * @param a the current allocation of the view
  801. * @param f the factory to use to rebuild if the view has children
  802. * @see View#removeUpdate
  803. */
  804. public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
  805. // update the pool of logical children
  806. Element elem = getElement();
  807. DocumentEvent.ElementChange ec = changes.getChange(elem);
  808. if (ec != null) {
  809. // the structure of this element changed.
  810. updateLogicalChildren(ec, f);
  811. }
  812. // find and forward if there is anything there to
  813. // forward to. If children were added then there was
  814. // a replacement of the removal range and there is no
  815. // need to forward.
  816. if (ec == null || (ec.getChildrenAdded().length == 0)) {
  817. int pos = changes.getOffset();
  818. int index = elem.getElementIndex(pos);
  819. View v = (View) layoutPool.elementAt(index);
  820. v.removeUpdate(changes, null, f);
  821. if (index > 0 && elem.getElement(index).getStartOffset() == pos) {
  822. ((View)layoutPool.elementAt(index - 1)).
  823. removeUpdate(changes, null, f);
  824. }
  825. }
  826. // force layout, should do something more intelligent about
  827. // incurring damage and triggering a new layout.
  828. layoutSpan = Integer.MAX_VALUE;
  829. preferenceChanged(null, true, true);
  830. if (a != null) {
  831. Rectangle alloc = getInsideAllocation(a);
  832. layout(alloc.width, alloc.height);
  833. Component host = getContainer();
  834. host.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
  835. }
  836. }
  837. /**
  838. * Gives notification from the document that attributes were changed
  839. * in a location that this view is responsible for.
  840. *
  841. * @param changes the change information from the associated document
  842. * @param a the current allocation of the view
  843. * @param f the factory to use to rebuild if the view has children
  844. * @see View#changedUpdate
  845. */
  846. public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
  847. // update any property settings stored
  848. setPropertiesFromAttributes();
  849. // update the pool of logical children
  850. Element elem = getElement();
  851. DocumentEvent.ElementChange ec = changes.getChange(elem);
  852. if (ec != null) {
  853. // the structure of this element changed.
  854. updateLogicalChildren(ec, f);
  855. }
  856. // forward to the logical children
  857. int p0 = changes.getOffset();
  858. int p1 = p0 + changes.getLength();
  859. int index0 = elem.getElementIndex(p0);
  860. int index1 = elem.getElementIndex(p1 - 1);
  861. // Check for case where p0 == p1 and they fall on a boundry.
  862. if (p0 == p1 && index1 < index0 && index0 > 0) {
  863. index0--;
  864. index1 = index0 + 1;
  865. }
  866. for (int i = index0; i <= index1; i++) {
  867. View v = (View) layoutPool.elementAt(i);
  868. v.changedUpdate(changes, null, f);
  869. }
  870. // force layout, should do something more intelligent about
  871. // incurring damage and triggering a new layout.
  872. layoutSpan = Integer.MAX_VALUE;
  873. preferenceChanged(null, true, true);
  874. if (a != null) {
  875. Rectangle alloc = getInsideAllocation(a);
  876. layout(alloc.width, alloc.height);
  877. Component host = getContainer();
  878. host.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
  879. }
  880. }
  881. /**
  882. * Overriden from CompositeView.
  883. */
  884. protected int getNextNorthSouthVisualPositionFrom(int pos, Position.Bias b,
  885. Shape a, int direction,
  886. Position.Bias[] biasRet)
  887. throws BadLocationException {
  888. int vIndex;
  889. if(pos == -1) {
  890. vIndex = (direction == SwingConstants.NORTH) ?
  891. getViewCount() - 1 : 0;
  892. }
  893. else {
  894. if(b == Position.Bias.Backward && pos > 0) {
  895. vIndex = getViewIndexAtPosition(pos - 1);
  896. }
  897. else {
  898. vIndex = getViewIndexAtPosition(pos);
  899. }
  900. if(direction == NORTH) {
  901. if(vIndex == 0) {
  902. return -1;
  903. }
  904. vIndex--;
  905. }
  906. else if(++vIndex >= getViewCount()) {
  907. return -1;
  908. }
  909. }
  910. // vIndex gives index of row to look in.
  911. JTextComponent text = (JTextComponent)getContainer();
  912. Caret c = text.getCaret();
  913. Point magicPoint;
  914. magicPoint = (c != null) ? c.getMagicCaretPosition() : null;
  915. int x;
  916. if(magicPoint == null) {
  917. Shape posBounds = text.getUI().modelToView(text, pos, b);
  918. if(posBounds == null) {
  919. x = 0;
  920. }
  921. else {
  922. x = posBounds.getBounds().x;
  923. }
  924. }
  925. else {
  926. x = magicPoint.x;
  927. }
  928. return getClosestPositionTo(pos, b, a, direction, biasRet, vIndex, x);
  929. }
  930. /**
  931. * Returns the closest model position to <code>x</code>.
  932. * <code>rowIndex</code> gives the index of the view that corresponds
  933. * that should be looked in.
  934. */
  935. // NOTE: This will not properly work if ParagraphView contains
  936. // other ParagraphViews. It won't raise, but this does not message
  937. // the children views with getNextVisualPositionFrom.
  938. protected int getClosestPositionTo(int pos, Position.Bias b, Shape a,
  939. int direction, Position.Bias[] biasRet,
  940. int rowIndex, int x)
  941. throws BadLocationException {
  942. JTextComponent text = (JTextComponent)getContainer();
  943. Document doc = getDocument();
  944. AbstractDocument aDoc = (doc instanceof AbstractDocument) ?
  945. (AbstractDocument)doc : null;
  946. View row = getView(rowIndex);
  947. int lastPos = -1;
  948. // This could be made better to check backward positions too.
  949. biasRet[0] = Position.Bias.Forward;
  950. for(int vc = 0, numViews = row.getViewCount(); vc < numViews; vc++) {
  951. View v = row.getView(vc);
  952. int start = v.getStartOffset();
  953. boolean ltr = (aDoc != null) ? aDoc.isLeftToRight
  954. (start, start + 1) : true;
  955. if(ltr) {
  956. lastPos = start;
  957. for(int end = v.getEndOffset(); lastPos < end; lastPos++) {
  958. if(text.modelToView(lastPos).getBounds().x >= x) {
  959. return lastPos;
  960. }
  961. }
  962. lastPos--;
  963. }
  964. else {
  965. for(lastPos = v.getEndOffset() - 1; lastPos >= start;
  966. lastPos--) {
  967. if(text.modelToView(lastPos).getBounds().x >= x) {
  968. return lastPos;
  969. }
  970. }
  971. lastPos++;
  972. }
  973. }
  974. if(lastPos == -1) {
  975. return getStartOffset();
  976. }
  977. return lastPos;
  978. }
  979. // --- variables -----------------------------------------------
  980. private int justification;
  981. private float lineSpacing;
  982. /** Indentation for the first line, from the left inset. */
  983. protected int firstLineIndent;
  984. /**
  985. * Used by the TabExpander functionality to determine
  986. * where to base the tab calculations. This is basically
  987. * the location of the left side of the paragraph.
  988. */
  989. private int tabBase;
  990. /**
  991. * Used by the layout process. The span holds the
  992. * length that has been formatted to.
  993. */
  994. private int layoutSpan;
  995. /**
  996. * These are the views that represent the child elements
  997. * of the element this view represents. These are not
  998. * directly children of this view. These are either
  999. * placed into the rows directly or used for the purpose
  1000. * of breaking into smaller chunks.
  1001. */
  1002. private Vector layoutPool;
  1003. /** Used for searching for a tab. */
  1004. static char[] tabChars;
  1005. /** Used for searching for a tab or decimal character. */
  1006. static char[] tabDecimalChars;
  1007. static {
  1008. tabChars = new char[1];
  1009. tabChars[0] = '\t';
  1010. tabDecimalChars = new char[2];
  1011. tabDecimalChars[0] = '\t';
  1012. tabDecimalChars[1] = '.';
  1013. }
  1014. /**
  1015. * Internally created view that has the purpose of holding
  1016. * the views that represent the children of the paragraph
  1017. * that have been arranged in rows.
  1018. */
  1019. class Row extends BoxView {
  1020. Row(Element elem) {
  1021. super(elem, View.X_AXIS);
  1022. }
  1023. /**
  1024. * This is reimplemented to do nothing since the
  1025. * paragraph fills in the row with its needed
  1026. * children.
  1027. */
  1028. protected void loadChildren(ViewFactory f) {
  1029. }
  1030. /**
  1031. * Fetches the attributes to use when rendering. This view
  1032. * isn't directly responsible for an element so it returns
  1033. * the outer classes attributes.
  1034. */
  1035. public AttributeSet getAttributes() {
  1036. return ParagraphView.this.getAttributes();
  1037. }
  1038. /**
  1039. * Determines the desired alignment for this view along an
  1040. * axis. This is implemented to give a horizontal alignment
  1041. * appropriate for the kind of justification being done.
  1042. *
  1043. * @param axis may be either View.X_AXIS or View.Y_AXIS
  1044. * @returns the desired alignment >= 0.0f && <= 1.0f. This should
  1045. * be a value between 0.0 and 1.0 where 0 indicates alignment at the
  1046. * origin and 1.0 indicates alignment to the full span
  1047. * away from the origin. An alignment of 0.5 would be the
  1048. * center of the view.
  1049. * @exception IllegalArgumentException for an invalid axis
  1050. */
  1051. protected void layout(int width, int height) {
  1052. Document doc = getDocument();
  1053. if (doc.getProperty(AbstractDocument.I18NProperty).equals(Boolean.TRUE)) {
  1054. int n = getViewCount();
  1055. if (n > 1) {
  1056. // REMIND (bcb) handle case of not an abstract document.
  1057. AbstractDocument d = (AbstractDocument)getDocument();
  1058. Element bidiRoot
  1059. = ((AbstractDocument)getElement().getDocument()).getBidiRootElement();
  1060. byte[] levels = new byte[n];
  1061. View[] reorder = new View[n];
  1062. for( int i=0; i<n; i++ ) {
  1063. View v = getView(i);
  1064. int bidiIndex =bidiRoot.getElementIndex(v.getStartOffset());
  1065. Element bidiElem = bidiRoot.getElement( bidiIndex );
  1066. levels[i] = (byte)StyleConstants.getBidiLevel(bidiElem.getAttributes());
  1067. reorder[i] = v;
  1068. }
  1069. Bidi.reorderVisually( levels, reorder );
  1070. replace(0, n, reorder);
  1071. }
  1072. }
  1073. super.layout(width, height);
  1074. }
  1075. public float getAlignment(int axis) {
  1076. if (axis == View.X_AXIS) {
  1077. switch (justification) {
  1078. case StyleConstants.ALIGN_LEFT:
  1079. return 0;
  1080. case StyleConstants.ALIGN_RIGHT:
  1081. return 1;
  1082. case StyleConstants.ALIGN_CENTER:
  1083. case StyleConstants.ALIGN_JUSTIFIED:
  1084. return 0.5f;
  1085. }
  1086. }
  1087. return super.getAlignment(axis);
  1088. }
  1089. /**
  1090. * Provides a mapping from the document model coordinate space
  1091. * to the coordinate space of the view mapped to it. This is
  1092. * implemented to let the superclass find the position along
  1093. * the major axis and the allocation of the row is used
  1094. * along the minor axis, so that even though the children
  1095. * are different heights they all get the same caret height.
  1096. *
  1097. * @param pos the position to convert
  1098. * @param a the allocated region to render into
  1099. * @return the bounding box of the given position
  1100. * @exception BadLocationException if the given position does not represent a
  1101. * valid location in the associated document
  1102. * @see View#modelToView
  1103. */
  1104. public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
  1105. Rectangle r = a.getBounds();
  1106. View v = getViewAtPosition(pos, r);
  1107. if ((v != null) && (!v.getElement().isLeaf())) {
  1108. // Don't adjust the height if the view represents a branch.
  1109. return super.modelToView(pos, a, b);
  1110. }
  1111. r = a.getBounds();
  1112. int height = r.height;
  1113. int y = r.y;
  1114. Shape loc = super.modelToView(pos, a, b);
  1115. r = loc.getBounds();
  1116. r.height = height;
  1117. r.y = y;
  1118. return r;
  1119. }
  1120. /**
  1121. * Range represented by a row in the paragraph is only
  1122. * a subset of the total range of the paragraph element.
  1123. * @see View#getRange
  1124. */
  1125. public int getStartOffset() {
  1126. int offs = Integer.MAX_VALUE;
  1127. int n = getViewCount();
  1128. for (int i = 0; i < n; i++) {
  1129. View v = getView(i);
  1130. offs = Math.min(offs, v.getStartOffset());
  1131. }
  1132. return offs;
  1133. }
  1134. public int getEndOffset() {
  1135. int offs = 0;
  1136. int n = getViewCount();
  1137. for (int i = 0; i < n; i++) {
  1138. View v = getView(i);
  1139. offs = Math.max(offs, v.getEndOffset());
  1140. }
  1141. return offs;
  1142. }
  1143. /**
  1144. * Perform layout for the minor axis of the box (i.e. the
  1145. * axis orthoginal to the axis that it represents). The results
  1146. * of the layout should be placed in the given arrays which represent
  1147. * the allocations to the children along the minor axis.
  1148. * <p>
  1149. * This is implemented to do a baseline layout of the children
  1150. * by calling BoxView.baselineLayout.
  1151. *
  1152. * @param targetSpan the total span given to the view, which
  1153. * whould be used to layout the children.
  1154. * @param axis the axis being layed out.
  1155. * @param offsets the offsets from the origin of the view for
  1156. * each of the child views. This is a return value and is
  1157. * filled in by the implementation of this method.
  1158. * @param spans the span of each child view. This is a return
  1159. * value and is filled in by the implementation of this method.
  1160. * @returns the offset and span for each child view in the
  1161. * offsets and spans parameters.
  1162. */
  1163. protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
  1164. baselineLayout(targetSpan, axis, offsets, spans);
  1165. }
  1166. protected SizeRequirements calculateMinorAxisRequirements(int axis,
  1167. SizeRequirements r) {
  1168. return baselineRequirements(axis, r);
  1169. }
  1170. /**
  1171. * Fetches the child view that represents the given position in
  1172. * the model. This is implemented to walk through the children
  1173. * looking for a range that contains the given position.
  1174. * @param pos The search position
  1175. * @param a The allocation to the box on entry, and the
  1176. * allocation of the view containing the position on exit.
  1177. * @returns The view representing the given position, or
  1178. * null if there isn't one.
  1179. */
  1180. protected View getViewAtPosition(int pos, Rectangle a) {
  1181. int n = getViewCount();
  1182. for (int i = 0; i < n; i++) {
  1183. View v = getView(i);
  1184. int p0 = v.getStartOffset();
  1185. int p1 = v.getEndOffset();
  1186. if ((pos >= p0) && (pos < p1)) {
  1187. // it's in this view.
  1188. if (a != null) {
  1189. this.childAllocation(i, a);
  1190. }
  1191. return v;
  1192. }
  1193. }
  1194. if (pos == getEndOffset()) {
  1195. // PENDING(bcb): This will probably want to choose the first
  1196. // if right to left.
  1197. View v = getView(n - 1);
  1198. if (a != null) {
  1199. this.childAllocation(n - 1, a);
  1200. }
  1201. return v;
  1202. }
  1203. return null;
  1204. }
  1205. /**
  1206. * Fetches the child view index representing the given position in
  1207. * the model.
  1208. *
  1209. * @param pos the position >= 0
  1210. * @returns index of the view representing the given position, or
  1211. * -1 if no view represents that position
  1212. */
  1213. protected int getViewIndexAtPosition(int pos) {
  1214. // This is expensive, but are views are not necessarily layed
  1215. // out in model order.
  1216. if(pos < getStartOffset() || pos >= getEndOffset())
  1217. return -1;
  1218. for(int counter = getViewCount() - 1; counter >= 0; counter--) {
  1219. View v = getView(counter);
  1220. if(pos >= v.getStartOffset() &&
  1221. pos < v.getEndOffset()) {
  1222. return counter;
  1223. }
  1224. }
  1225. return -1;
  1226. }
  1227. }
  1228. }