1. /*
  2. * @(#)CompositeView.java 1.54 00/02/02
  3. *
  4. * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
  5. *
  6. * This software is the proprietary information of Sun Microsystems, Inc.
  7. * Use is subject to license terms.
  8. *
  9. */
  10. package javax.swing.text;
  11. import java.util.Vector;
  12. import java.awt.*;
  13. import javax.swing.event.*;
  14. import javax.swing.SwingConstants;
  15. /**
  16. * A view that is composed of other views (has children).
  17. * As a container of children, the composite view needs
  18. * to provide:
  19. *
  20. * <dl>
  21. * <dt><b>services to manage the collection of children</b>
  22. * <dd>The following methods can be used to manage the
  23. * collection.
  24. * <ul>
  25. * <li><a href="#removeAll">removeAll</a>
  26. * <li><a href="#insert">insert</a>
  27. * <li><a href="#append">append</a>
  28. * <li><a href="#replace">replace</a>
  29. * <li><a href="#getViewCount">getViewCount</a>
  30. * <li><a href="#getView">getView</a>
  31. * <li><a href="#loadChildren">loadChildren</a>
  32. * </ul>
  33. *
  34. * <dt><b>layout of the children</b>
  35. * <dd>This class does not implement a layout policy
  36. * as it is abstract. A subclass will determine how
  37. * the children are laid out by implementing the
  38. * <a href="View.html#setSize(float, float)">setSize</a> method to position
  39. * the children when the size has been changed.
  40. *
  41. * <dt><b>paint the children</b>
  42. * <dd>This class does not attempt to paint the
  43. * children. Subclasses will want to use the
  44. * layout information and call paint on the children
  45. * that are visible (intersect the clipping region)
  46. * with the Shape argument set to the location of the
  47. * child view.
  48. *
  49. * <dt><b>propagation of
  50. * <a href="../event/DocumentEvent.html">DocumentEvent</a>
  51. * information to the appropriate children.</b>
  52. *
  53. * <dt>propagation of model/view translation to the
  54. * proper child.
  55. * </dl>
  56. *
  57. * @author Timothy Prinzing
  58. * @version 1.54 02/02/00
  59. */
  60. public abstract class CompositeView extends View {
  61. /**
  62. * Constructs a CompositeView for the given element.
  63. *
  64. * @param elem the element this view is responsible for
  65. */
  66. public CompositeView(Element elem) {
  67. super(elem);
  68. children = new View[1];
  69. nchildren = 0;
  70. childAlloc = new Rectangle();
  71. }
  72. /**
  73. * Loads all of the children to initialize the view.
  74. * This is called by the <a href="#setParent">setParent</a>
  75. * method. Subclasses can reimplement this to initialize
  76. * their child views in a different manner. The default
  77. * implementation creates a child view for each
  78. * child element.
  79. *
  80. * @param f the view factory
  81. * @see #setParent
  82. */
  83. protected void loadChildren(ViewFactory f) {
  84. if (f == null) {
  85. // No factory. This most likely indicates the parent view
  86. // has changed out from under us, bail!
  87. return;
  88. }
  89. Element e = getElement();
  90. int n = e.getElementCount();
  91. if (n > 0) {
  92. View[] added = new View[n];
  93. for (int i = 0; i < n; i++) {
  94. added[i] = f.create(e.getElement(i));
  95. }
  96. replace(0, 0, added);
  97. }
  98. }
  99. // --- View methods ---------------------------------------------
  100. /**
  101. * Sets the parent of the view.
  102. * This is reimplemented to provide the superclass
  103. * behavior as well as calling the <code>loadChildren</code>
  104. * method if this view does not already have children.
  105. * The children should not be loaded in the
  106. * constructor because the act of setting the parent
  107. * may cause them to try to search up the hierarchy
  108. * (to get the hosting Container for example).
  109. * If this view has children (the view is being moved
  110. * from one place in the view hierarchy to another),
  111. * the <code>loadChildren</code> method will not be called.
  112. *
  113. * @param parent the parent of the view, null if none
  114. */
  115. public void setParent(View parent) {
  116. super.setParent(parent);
  117. if ((parent != null) && (nchildren == 0)) {
  118. ViewFactory f = getViewFactory();
  119. loadChildren(f);
  120. }
  121. }
  122. /**
  123. * Returns the number of child views of this view.
  124. *
  125. * @return the number of views >= 0
  126. * @see #getView
  127. */
  128. public int getViewCount() {
  129. return nchildren;
  130. }
  131. /**
  132. * Gets the n-th view in this container.
  133. *
  134. * @param n the number of the view to get, >= 0 && < getViewCount()
  135. * @return the view
  136. */
  137. public View getView(int n) {
  138. return children[n];
  139. }
  140. /**
  141. * Replace child views. If there are no views to remove
  142. * this acts as an insert. If there are no views to
  143. * add this acts as a remove. Views being removed will
  144. * have the parent set to null, and the internal reference
  145. * to them removed so that they can be garbage collected.
  146. *
  147. * @param index the starting index into the child views to insert
  148. * the new views. This should be a value >= 0 and <= getViewCount.
  149. * @param length the number of existing child views to remove.
  150. * This should be a value >= 0 and <= (getViewCount() - offset).
  151. * @param views the child views to add. This value can be null
  152. * to indicate no children are being added (useful to remove).
  153. */
  154. public void replace(int offset, int length, View[] views) {
  155. // make sure an array exists
  156. if (views == null) {
  157. views = ZERO;
  158. }
  159. // update parent reference on removed views
  160. for (int i = offset; i < offset + length; i++) {
  161. children[i].setParent(null);
  162. children[i] = null;
  163. }
  164. // update the array
  165. int delta = views.length - length;
  166. int src = offset + length;
  167. int nmove = nchildren - src;
  168. int dest = src + delta;
  169. if ((nchildren + delta) >= children.length) {
  170. // need to grow the array
  171. int newLength = Math.max(2*children.length, nchildren + delta);
  172. View[] newChildren = new View[newLength];
  173. System.arraycopy(children, 0, newChildren, 0, offset);
  174. System.arraycopy(views, 0, newChildren, offset, views.length);
  175. System.arraycopy(children, src, newChildren, dest, nmove);
  176. children = newChildren;
  177. } else {
  178. // patch the existing array
  179. System.arraycopy(children, src, children, dest, nmove);
  180. System.arraycopy(views, 0, children, offset, views.length);
  181. }
  182. nchildren = nchildren + delta;
  183. // update parent reference on added views
  184. for (int i = 0; i < views.length; i++) {
  185. views[i].setParent(this);
  186. }
  187. }
  188. /**
  189. * Fetches the allocation for the given child view.
  190. * This enables finding out where various views
  191. * are located.
  192. *
  193. * @param index the index of the child, >= 0 && < getViewCount()
  194. * @param a the allocation to this view.
  195. * @return the allocation to the child
  196. */
  197. public Shape getChildAllocation(int index, Shape a) {
  198. Rectangle alloc = getInsideAllocation(a);
  199. childAllocation(index, alloc);
  200. return alloc;
  201. }
  202. /**
  203. * Provides a mapping from the document model coordinate space
  204. * to the coordinate space of the view mapped to it.
  205. *
  206. * @param pos the position to convert >= 0
  207. * @param a the allocated region to render into
  208. * @return the bounding box of the given position
  209. * @exception BadLocationException if the given position does
  210. * not represent a valid location in the associated document
  211. * @see View#modelToView
  212. */
  213. public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
  214. boolean isBackward = (b == Position.Bias.Backward);
  215. int testPos = (isBackward) ? Math.max(0, pos - 1) : pos;
  216. if(isBackward && testPos < getStartOffset()) {
  217. return null;
  218. }
  219. int vIndex = getViewIndexAtPosition(testPos);
  220. if ((vIndex != -1) && (vIndex < getViewCount())) {
  221. View v = getView(vIndex);
  222. if(v != null && testPos >= v.getStartOffset() &&
  223. testPos < v.getEndOffset()) {
  224. Shape retShape = v.modelToView(pos, getChildAllocation(vIndex, a), b);
  225. if(retShape == null && v.getEndOffset() == pos) {
  226. if(++vIndex < getViewCount()) {
  227. v = getView(vIndex);
  228. retShape = v.modelToView(pos, getChildAllocation(vIndex, a), b);
  229. }
  230. }
  231. return retShape;
  232. }
  233. }
  234. throw new BadLocationException("Position not represented by view",
  235. pos);
  236. }
  237. /**
  238. * Provides a mapping from the document model coordinate space
  239. * to the coordinate space of the view mapped to it.
  240. *
  241. * @param p0 the position to convert >= 0
  242. * @param b0 the bias toward the previous character or the
  243. * next character represented by p0, in case the
  244. * position is a boundary of two views.
  245. * @param p1 the position to convert >= 0
  246. * @param b1 the bias toward the previous character or the
  247. * next character represented by p1, in case the
  248. * position is a boundary of two views.
  249. * @param a the allocated region to render into
  250. * @return the bounding box of the given position is returned
  251. * @exception BadLocationException if the given position does
  252. * not represent a valid location in the associated document
  253. * @exception IllegalArgumentException for an invalid bias argument
  254. * @see View#viewToModel
  255. */
  256. public Shape modelToView(int p0, Position.Bias b0, int p1, Position.Bias b1, Shape a) throws BadLocationException {
  257. if (p0 == getStartOffset() && p1 == getEndOffset()) {
  258. return a;
  259. }
  260. Rectangle alloc = getInsideAllocation(a);
  261. Rectangle r0 = new Rectangle(alloc);
  262. View v0 = getViewAtPosition((b0 == Position.Bias.Backward) ?
  263. Math.max(0, p0 - 1) : p0, r0);
  264. Rectangle r1 = new Rectangle(alloc);
  265. View v1 = getViewAtPosition((b1 == Position.Bias.Backward) ?
  266. Math.max(0, p1 - 1) : p1, r1);
  267. if (v0 == v1) {
  268. if (v0 == null) {
  269. return a;
  270. }
  271. // Range contained in one view
  272. return v0.modelToView(p0, b0, p1, b1, r0);
  273. }
  274. // Straddles some views.
  275. int viewCount = getViewCount();
  276. int counter = 0;
  277. while (counter < viewCount) {
  278. View v;
  279. // Views may not be in same order as model.
  280. // v0 or v1 may be null if there is a gap in the range this
  281. // view contains.
  282. if ((v = getView(counter)) == v0 || v == v1) {
  283. View endView;
  284. Rectangle retRect;
  285. Rectangle tempRect = new Rectangle();
  286. if (v == v0) {
  287. retRect = v0.modelToView(p0, b0, v0.getEndOffset(),
  288. Position.Bias.Backward, r0).
  289. getBounds();
  290. endView = v1;
  291. }
  292. else {
  293. retRect = v1.modelToView(v1.getStartOffset(),
  294. Position.Bias.Forward,
  295. p1, b1, r1).getBounds();
  296. endView = v0;
  297. }
  298. // Views entirely covered by range.
  299. while (++counter < viewCount &&
  300. (v = getView(counter)) != endView) {
  301. tempRect.setBounds(alloc);
  302. childAllocation(counter, tempRect);
  303. retRect.add(tempRect);
  304. }
  305. // End view.
  306. if (endView != null) {
  307. Shape endShape;
  308. if (endView == v1) {
  309. endShape = v1.modelToView(v1.getStartOffset(),
  310. Position.Bias.Forward,
  311. p1, b1, r1);
  312. }
  313. else {
  314. endShape = v0.modelToView(p0, b0, v0.getEndOffset(),
  315. Position.Bias.Backward, r0);
  316. }
  317. if (endShape instanceof Rectangle) {
  318. retRect.add((Rectangle)endShape);
  319. }
  320. else {
  321. retRect.add(endShape.getBounds());
  322. }
  323. }
  324. return retRect;
  325. }
  326. counter++;
  327. }
  328. throw new BadLocationException("Position not represented by view", p0);
  329. }
  330. /**
  331. * Provides a mapping from the view coordinate space to the logical
  332. * coordinate space of the model.
  333. *
  334. * @param x x coordinate of the view location to convert >= 0
  335. * @param y y coordinate of the view location to convert >= 0
  336. * @param a the allocated region to render into
  337. * @return the location within the model that best represents the
  338. * given point in the view >= 0
  339. * @see View#viewToModel
  340. */
  341. public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
  342. Rectangle alloc = getInsideAllocation(a);
  343. if (isBefore((int) x, (int) y, alloc)) {
  344. // point is before the range represented
  345. int retValue = -1;
  346. try {
  347. retValue = getNextVisualPositionFrom(-1, Position.Bias.Forward,
  348. a, EAST, bias);
  349. } catch (BadLocationException ble) { }
  350. catch (IllegalArgumentException iae) { }
  351. if(retValue == -1) {
  352. retValue = getStartOffset();
  353. bias[0] = Position.Bias.Forward;
  354. }
  355. return retValue;
  356. } else if (isAfter((int) x, (int) y, alloc)) {
  357. // point is after the range represented.
  358. int retValue = -1;
  359. try {
  360. retValue = getNextVisualPositionFrom(-1, Position.Bias.Forward,
  361. a, WEST, bias);
  362. } catch (BadLocationException ble) { }
  363. catch (IllegalArgumentException iae) { }
  364. if(retValue == -1) {
  365. // NOTE: this could actually use end offset with backward.
  366. retValue = getEndOffset() - 1;
  367. bias[0] = Position.Bias.Forward;
  368. }
  369. return retValue;
  370. } else {
  371. // locate the child and pass along the request
  372. View v = getViewAtPoint((int) x, (int) y, alloc);
  373. if (v != null) {
  374. return v.viewToModel(x, y, alloc, bias);
  375. }
  376. }
  377. return -1;
  378. }
  379. /**
  380. * Provides a way to determine the next visually represented model
  381. * location that one might place a caret. Some views may not be visible,
  382. * they might not be in the same order found in the model, or they just
  383. * might not allow access to some of the locations in the model.
  384. *
  385. * @param pos the position to convert >= 0
  386. * @param a the allocated region to render into
  387. * @param direction the direction from the current position that can
  388. * be thought of as the arrow keys typically found on a keyboard.
  389. * This may be SwingConstants.WEST, SwingConstants.EAST,
  390. * SwingConstants.NORTH, or SwingConstants.SOUTH.
  391. * @return the location within the model that best represents the next
  392. * location visual position.
  393. * @exception BadLocationException
  394. * @exception IllegalArgumentException for an invalid direction
  395. */
  396. public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a,
  397. int direction, Position.Bias[] biasRet)
  398. throws BadLocationException {
  399. Rectangle alloc = getInsideAllocation(a);
  400. switch (direction) {
  401. case NORTH:
  402. return getNextNorthSouthVisualPositionFrom(pos, b, a, direction,
  403. biasRet);
  404. case SOUTH:
  405. return getNextNorthSouthVisualPositionFrom(pos, b, a, direction,
  406. biasRet);
  407. case EAST:
  408. return getNextEastWestVisualPositionFrom(pos, b, a, direction,
  409. biasRet);
  410. case WEST:
  411. return getNextEastWestVisualPositionFrom(pos, b, a, direction,
  412. biasRet);
  413. default:
  414. throw new IllegalArgumentException("Bad direction: " + direction);
  415. }
  416. }
  417. /**
  418. * Returns the child view index representing the given position in
  419. * the model. This is implemented to call the getViewIndexByPosition
  420. * method for backward compatibility.
  421. *
  422. * @param pos the position >= 0
  423. * @returns index of the view representing the given position, or
  424. * -1 if no view represents that position
  425. */
  426. public int getViewIndex(int pos, Position.Bias b) {
  427. if(b == Position.Bias.Backward) {
  428. pos -= 1;
  429. }
  430. if ((pos >= getStartOffset()) && (pos < getEndOffset())) {
  431. return getViewIndexAtPosition(pos);
  432. }
  433. return -1;
  434. }
  435. // --- local methods ----------------------------------------------------
  436. /**
  437. * Tests whether a point lies before the rectangle range.
  438. *
  439. * @param x the X coordinate >= 0
  440. * @param y the Y coordinate >= 0
  441. * @param alloc the rectangle
  442. * @return true if the point is before the specified range
  443. */
  444. protected abstract boolean isBefore(int x, int y, Rectangle alloc);
  445. /**
  446. * Tests whether a point lies after the rectangle range.
  447. *
  448. * @param x the X coordinate >= 0
  449. * @param y the Y coordinate >= 0
  450. * @param alloc the rectangle
  451. * @return true if the point is after the specified range
  452. */
  453. protected abstract boolean isAfter(int x, int y, Rectangle alloc);
  454. /**
  455. * Fetches the child view at the given point.
  456. *
  457. * @param x the X coordinate >= 0
  458. * @param y the Y coordinate >= 0
  459. * @param alloc the parent's allocation on entry, which should
  460. * be changed to the child's allocation on exit
  461. * @return the child view
  462. */
  463. protected abstract View getViewAtPoint(int x, int y, Rectangle alloc);
  464. /**
  465. * Returns the allocation for a given child.
  466. *
  467. * @param index the index of the child, >= 0 && < getViewCount()
  468. * @param a the allocation to the interior of the box on entry,
  469. * and the allocation of the child view at the index on exit.
  470. */
  471. protected abstract void childAllocation(int index, Rectangle a);
  472. /**
  473. * Fetches the child view that represents the given position in
  474. * the model. This is implemented to fetch the view in the case
  475. * where there is a child view for each child element.
  476. *
  477. * @param pos the position >= 0
  478. * @param a the allocation to the interior of the box on entry,
  479. * and the allocation of the view containing the position on exit
  480. * @returns the view representing the given position, or
  481. * null if there isn't one
  482. */
  483. protected View getViewAtPosition(int pos, Rectangle a) {
  484. int index = getViewIndexAtPosition(pos);
  485. if ((index >= 0) && (index < getViewCount())) {
  486. View v = getView(index);
  487. if (a != null) {
  488. childAllocation(index, a);
  489. }
  490. return v;
  491. }
  492. return null;
  493. }
  494. /**
  495. * Fetches the child view index representing the given position in
  496. * the model. This is implemented to fetch the view in the case
  497. * where there is a child view for each child element.
  498. *
  499. * @param pos the position >= 0
  500. * @returns index of the view representing the given position, or
  501. * -1 if no view represents that position
  502. */
  503. protected int getViewIndexAtPosition(int pos) {
  504. Element elem = getElement();
  505. return elem.getElementIndex(pos);
  506. }
  507. /**
  508. * Translates the immutable allocation given to the view
  509. * to a mutable allocation that represents the interior
  510. * allocation (i.e. the bounds of the given allocation
  511. * with the top, left, bottom, and right insets removed.
  512. * It is expected that the returned value would be further
  513. * mutated to represent an allocation to a child view.
  514. * This is implemented to reuse an instance variable so
  515. * it avoids creating excessive Rectangles. Typically
  516. * the result of calling this method would be fed to
  517. * the childAllocation method.
  518. *
  519. * @param a The allocation given to the view.
  520. * @returns The allocation that represents the inside of the
  521. * view after the margins have all been removed. If the
  522. * given allocation was null, the return value is null.
  523. */
  524. protected Rectangle getInsideAllocation(Shape a) {
  525. if (a != null) {
  526. // get the bounds, hopefully without allocating
  527. // a new rectangle. The Shape argument should
  528. // not be modified... we copy it into the
  529. // child allocation.
  530. Rectangle alloc;
  531. if (a instanceof Rectangle) {
  532. alloc = (Rectangle) a;
  533. } else {
  534. alloc = a.getBounds();
  535. }
  536. childAlloc.setBounds(alloc);
  537. childAlloc.x += left;
  538. childAlloc.y += top;
  539. childAlloc.width -= left + right;
  540. childAlloc.height -= top + bottom;
  541. return childAlloc;
  542. }
  543. return null;
  544. }
  545. /**
  546. * Sets the insets from the paragraph attributes specified in
  547. * the given attributes.
  548. *
  549. * @param attr the attributes
  550. */
  551. protected void setParagraphInsets(AttributeSet attr) {
  552. // Since version 1.1 doesn't have scaling and assumes
  553. // a pixel is equal to a point, we just cast the point
  554. // sizes to integers.
  555. top = (short) StyleConstants.getSpaceAbove(attr);
  556. left = (short) StyleConstants.getLeftIndent(attr);
  557. bottom = (short) StyleConstants.getSpaceBelow(attr);
  558. right = (short) StyleConstants.getRightIndent(attr);
  559. }
  560. /**
  561. * Sets the insets for the view.
  562. *
  563. * @param top the top inset >= 0
  564. * @param left the left inset >= 0
  565. * @param bottom the bottom inset >= 0
  566. * @param right the right inset >= 0
  567. */
  568. protected void setInsets(short top, short left, short bottom, short right) {
  569. this.top = top;
  570. this.left = left;
  571. this.right = right;
  572. this.bottom = bottom;
  573. }
  574. /**
  575. * Gets the left inset.
  576. *
  577. * @return the inset >= 0
  578. */
  579. protected short getLeftInset() {
  580. return left;
  581. }
  582. /**
  583. * Gets the right inset.
  584. *
  585. * @return the inset >= 0
  586. */
  587. protected short getRightInset() {
  588. return right;
  589. }
  590. /**
  591. * Gets the top inset.
  592. *
  593. * @return the inset >= 0
  594. */
  595. protected short getTopInset() {
  596. return top;
  597. }
  598. /**
  599. * Gets the bottom inset.
  600. *
  601. * @return the inset >= 0
  602. */
  603. protected short getBottomInset() {
  604. return bottom;
  605. }
  606. /**
  607. * Returns the next visual position for the cursor, in either the
  608. * east or west direction.
  609. *
  610. * @return next position west of the passed in position.
  611. */
  612. // PENDING: This only checks the next element. If one of the children
  613. // Views returns -1, it should continue checking all the children.
  614. // PENDING(sky): This name sucks! Come up with a better one!
  615. protected int getNextNorthSouthVisualPositionFrom(int pos, Position.Bias b,
  616. Shape a, int direction,
  617. Position.Bias[] biasRet)
  618. throws BadLocationException {
  619. if(getViewCount() == 0) {
  620. // Nothing to do.
  621. return pos;
  622. }
  623. boolean isNorth = (direction == NORTH);
  624. Rectangle alloc = getInsideAllocation(a);
  625. int retValue;
  626. if(pos == -1) {
  627. View v = (isNorth) ? getView(getViewCount() - 1) : getView(0);
  628. childAllocation(0, alloc);
  629. retValue = v.getNextVisualPositionFrom(pos, b, alloc, direction,
  630. biasRet);
  631. }
  632. else {
  633. int vIndex;
  634. if(b == Position.Bias.Backward && pos > 0) {
  635. vIndex = getViewIndexAtPosition(pos - 1);
  636. }
  637. else {
  638. vIndex = getViewIndexAtPosition(pos);
  639. }
  640. View v = getView(vIndex);
  641. childAllocation(vIndex, alloc);
  642. retValue = v.getNextVisualPositionFrom(pos, b, alloc, direction,
  643. biasRet);
  644. if(retValue == -1) {
  645. if((isNorth && --vIndex >= 0) ||
  646. (!isNorth && ++vIndex < getViewCount())) {
  647. v = getView(vIndex);
  648. alloc = getInsideAllocation(a);
  649. childAllocation(vIndex, alloc);
  650. retValue = v.getNextVisualPositionFrom(-1, b, alloc,
  651. direction, biasRet);
  652. }
  653. }
  654. }
  655. return retValue;
  656. }
  657. /**
  658. * Returns the next visual position for the cursor, in either the
  659. * east or west direction.
  660. *
  661. * @return next position west of the passed in position.
  662. */
  663. // PENDING: This only checks the next element. If one of the children
  664. // Views returns -1, it should continue checking all the children.
  665. // PENDING(sky): This name sucks! Come up with a better one!
  666. protected int getNextEastWestVisualPositionFrom(int pos, Position.Bias b,
  667. Shape a,
  668. int direction,
  669. Position.Bias[] biasRet)
  670. throws BadLocationException {
  671. if(getViewCount() == 0) {
  672. // Nothing to do.
  673. return pos;
  674. }
  675. boolean isEast = (direction == EAST);
  676. Rectangle alloc = getInsideAllocation(a);
  677. int retValue;
  678. int increment = (isEast) ? 1 : -1;
  679. if(pos == -1) {
  680. View v = (isEast) ? getView(0) : getView(getViewCount() - 1);
  681. childAllocation(0, alloc);
  682. retValue = v.getNextVisualPositionFrom(pos, b, alloc,
  683. direction, biasRet);
  684. if(retValue == -1 && isEast && getViewCount() > 1) {
  685. // Special case that should ONLY happen if first view
  686. // isn't valid (can happen when end position is put at
  687. // beginning of line.
  688. v = getView(1);
  689. alloc = getInsideAllocation(a);
  690. childAllocation(1, alloc);
  691. retValue = v.getNextVisualPositionFrom(-1, biasRet[0], alloc,
  692. direction, biasRet);
  693. }
  694. }
  695. else {
  696. int vIndex;
  697. if(b == Position.Bias.Backward) {
  698. vIndex = getViewIndexAtPosition(Math.max(getStartOffset(),
  699. pos - 1));
  700. }
  701. else {
  702. vIndex = getViewIndexAtPosition(pos);
  703. }
  704. View v = getView(vIndex);
  705. childAllocation(vIndex, alloc);
  706. retValue = v.getNextVisualPositionFrom(pos, b, alloc,
  707. direction, biasRet);
  708. if(retValue == -1) {
  709. if(flipEastAndWestAtEnds(pos, b)) {
  710. increment *= -1;
  711. }
  712. vIndex += increment;
  713. if(vIndex >= 0 && vIndex < getViewCount()) {
  714. v = getView(vIndex);
  715. alloc = getInsideAllocation(a);
  716. childAllocation(vIndex, alloc);
  717. retValue = v.getNextVisualPositionFrom
  718. (-1, b, alloc, direction, biasRet);
  719. // If there is a bias change, it is a fake position
  720. // and we should skip it. This is usually the result
  721. // of two elements side be side flowing the same way.
  722. if(retValue == pos && retValue != -1 && biasRet[0] != b) {
  723. alloc = getInsideAllocation(a);
  724. childAllocation(vIndex, alloc);
  725. retValue = v.getNextVisualPositionFrom
  726. (retValue, biasRet[0], alloc, direction,
  727. biasRet);
  728. }
  729. }
  730. }
  731. else {
  732. if(flipEastAndWestAtEnds(pos, b)) {
  733. increment *= -1;
  734. }
  735. vIndex += increment;
  736. if(biasRet[0] != b &&
  737. ((increment == 1 && v.getEndOffset() == retValue) ||
  738. (increment == -1 && v.getStartOffset() == retValue)) &&
  739. vIndex >= 0 && vIndex < getViewCount()) {
  740. // Reached the end of a view, make sure the next view
  741. // is a different direction.
  742. v = getView(vIndex);
  743. alloc = getInsideAllocation(a);
  744. childAllocation(vIndex, alloc);
  745. Position.Bias originalBias = biasRet[0];
  746. int nextPos = v.getNextVisualPositionFrom
  747. (-1, b, alloc, direction, biasRet);
  748. if(biasRet[0] == b) {
  749. retValue = nextPos;
  750. }
  751. else {
  752. biasRet[0] = originalBias;
  753. }
  754. }
  755. }
  756. }
  757. return retValue;
  758. }
  759. /**
  760. * Subclasses may wish to subclass this and conditionally return
  761. * true based on the position. A return value of true indicates that
  762. * when a View returns -1 from getNextVisualPositionFrom the next
  763. * view for east should be the current index offset by -1, and for
  764. * west it means offset by 1. The normal direction (for left to
  765. * right text) is to offset east by 1 and west by -1.
  766. *
  767. * @return false
  768. */
  769. protected boolean flipEastAndWestAtEnds(int position,
  770. Position.Bias bias) {
  771. return false;
  772. }
  773. // ---- member variables ---------------------------------------------
  774. private static View[] ZERO = new View[0];
  775. private View[] children;
  776. private int nchildren;
  777. private short left;
  778. private short right;
  779. private short top;
  780. private short bottom;
  781. private Rectangle childAlloc;
  782. }