1. /*
  2. * @(#)CompositeView.java 1.46 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.awt.*;
  10. import javax.swing.event.*;
  11. import javax.swing.SwingConstants;
  12. /**
  13. * A view that is composed of other views (has children).
  14. * As a container of children, the composite view needs
  15. * to provide:
  16. *
  17. * <dl>
  18. * <dt><b>services to manage the collection of children</b>
  19. * <dd>The following methods can be used to manage the
  20. * collection.
  21. * <ul>
  22. * <li><a href="#removeAll">removeAll</a>
  23. * <li><a href="#insert">insert</a>
  24. * <li><a href="#append">append</a>
  25. * <li><a href="#replace">replace</a>
  26. * <li><a href="#getViewCount">getViewCount</a>
  27. * <li><a href="#getView">getView</a>
  28. * <li><a href="#loadChildren">loadChildren</a>
  29. * </ul>
  30. *
  31. * <dt><b>layout of the children</b>
  32. * <dd>This class does not implement a layout policy
  33. * as it is abstract. A subclass will determine how
  34. * the children are laid out by implementing the
  35. * <a href="View#setSize">setSize</a> method to position
  36. * the children when the size has been changed.
  37. *
  38. * <dt><b>paint the children</b>
  39. * <dd>This class does not attempt to paint the
  40. * children. Subclasses will want to use the
  41. * layout information and call paint on the children
  42. * that are visible (intersect the clipping region)
  43. * with the Shape argument set to the location of the
  44. * child view.
  45. *
  46. * <dt><b>propagation of
  47. * <a href="javax.swing.event.DocumentEvent">DocumentEvent</a>
  48. * information to the appropriate children.</b>
  49. *
  50. * <dt>propagation of model/view translation to the
  51. * proper child.
  52. * </dl>
  53. *
  54. * @author Timothy Prinzing
  55. * @version 1.46 11/29/01
  56. */
  57. public abstract class CompositeView extends View {
  58. /**
  59. * Constructs a CompositeView for the given element.
  60. *
  61. * @param elem the element this view is responsible for
  62. */
  63. public CompositeView(Element elem) {
  64. super(elem);
  65. children = new View[1];
  66. nchildren = 0;
  67. childAlloc = new Rectangle();
  68. }
  69. /**
  70. * Loads all of the children to initialize the view.
  71. * This is called by the <a href="#setParent">setParent</a>
  72. * method. Subclasses can reimplement this to initialize
  73. * their child views in a different manner. The default
  74. * implementation creates a child view for each
  75. * child element.
  76. *
  77. * @param f the view factory
  78. * @see #setParent
  79. */
  80. protected void loadChildren(ViewFactory f) {
  81. Element e = getElement();
  82. int n = e.getElementCount();
  83. if (n > 0) {
  84. View[] added = new View[n];
  85. for (int i = 0; i < n; i++) {
  86. added[i] = f.create(e.getElement(i));
  87. }
  88. replace(0, 0, added);
  89. }
  90. }
  91. /**
  92. * Removes all of the children.
  93. */
  94. public void removeAll() {
  95. replace(0, nchildren, ZERO);
  96. }
  97. /**
  98. * Removes one of the children at the given position.
  99. */
  100. /*public*/ void remove(int i) {
  101. replace(i, 1, ZERO);
  102. }
  103. /**
  104. * Inserts a single child view. This is a convenience
  105. * call to replace.
  106. *
  107. * @param offs the offset of the view to insert before >= 0
  108. * @param v the view
  109. * @see #replace
  110. */
  111. public void insert(int offs, View v) {
  112. View[] one = new View[1];
  113. one[0] = v;
  114. replace(offs, 0, one);
  115. }
  116. /**
  117. * Appends a single child view. This is a convenience
  118. * call to replace.
  119. *
  120. * @param v the view
  121. * @see #replace
  122. */
  123. public void append(View v) {
  124. View[] one = new View[1];
  125. one[0] = v;
  126. replace(nchildren, 0, one);
  127. }
  128. /**
  129. * Replace child views. If there are no views to remove
  130. * this acts as an insert. If there are no views to
  131. * add this acts as a remove. Views being removed will
  132. * have the parent set to null, and the internal reference
  133. * to them removed so that they can be garbage collected.
  134. *
  135. * @param index the starting index into the child views to insert
  136. * the new views >= 0
  137. * @param length the number of existing child views to remove >= 0
  138. * @param views the child views to add
  139. */
  140. public void replace(int offset, int length, View[] views) {
  141. // update parent reference on removed views
  142. for (int i = offset; i < offset + length; i++) {
  143. children[i].setParent(null);
  144. children[i] = null;
  145. }
  146. // update the array
  147. int delta = views.length - length;
  148. int src = offset + length;
  149. int nmove = nchildren - src;
  150. int dest = src + delta;
  151. if ((nchildren + delta) >= children.length) {
  152. // need to grow the array
  153. int newLength = Math.max(2*children.length, nchildren + delta);
  154. View[] newChildren = new View[newLength];
  155. System.arraycopy(children, 0, newChildren, 0, offset);
  156. System.arraycopy(views, 0, newChildren, offset, views.length);
  157. System.arraycopy(children, src, newChildren, dest, nmove);
  158. children = newChildren;
  159. } else {
  160. // patch the existing array
  161. System.arraycopy(children, src, children, dest, nmove);
  162. System.arraycopy(views, 0, children, offset, views.length);
  163. }
  164. nchildren = nchildren + delta;
  165. // update parent reference on added views
  166. for (int i = 0; i < views.length; i++) {
  167. views[i].setParent(this);
  168. }
  169. }
  170. /**
  171. * Updates the child views in response to receiving notification
  172. * that the model changed, and there is change record for the
  173. * element this view is responsible for. This is implemented
  174. * to assume the child views are directly responsible for the
  175. * child elements of the element this view represents. The
  176. * ViewFactory is used to create child views for each element
  177. * specified as added in the ElementChange, starting at the
  178. * index specified in the given ElementChange. The number of
  179. * child views representing the removed elements specified are
  180. * removed.
  181. *
  182. * @param ec The change information for the element this view
  183. * is responsible for. This should not be null if this method
  184. * gets called.
  185. * @param e the change information from the associated document
  186. * @param f the factory to use to build child views
  187. * @return whether or not the child views represent the
  188. * child elements of the element this view is responsible
  189. * for. Some views create children that represent a portion
  190. * of the element they are responsible for, and should return
  191. * false. This information is used to determine if views
  192. * in the range of the added elements should be forwarded to
  193. * or not.
  194. * @see #insertUpdate
  195. * @see #removeUpdate
  196. * @see #changedUpdate
  197. */
  198. /*protected*/ boolean updateChildren(DocumentEvent.ElementChange ec,
  199. DocumentEvent e, ViewFactory f) {
  200. // the structure of this element changed.
  201. Element[] removedElems = ec.getChildrenRemoved();
  202. Element[] addedElems = ec.getChildrenAdded();
  203. View[] added = new View[addedElems.length];
  204. for (int i = 0; i < addedElems.length; i++) {
  205. added[i] = f.create(addedElems[i]);
  206. }
  207. int index = ec.getIndex();
  208. replace(index, removedElems.length, added);
  209. return true;
  210. }
  211. /**
  212. * Forward the given DocumentEvent to the child views
  213. * that need to be notified of the change to the model.
  214. * If there were changes to the element this view is
  215. * responsible for, that should be considered when
  216. * forwarding (i.e. new child views should not get
  217. * notified).
  218. *
  219. * @param ec changes to the element this view is responsible
  220. * for (may be null if there were no changes).
  221. * @param e the change information from the associated document
  222. * @param a the current allocation of the view
  223. * @param f the factory to use to rebuild if the view has children
  224. * @see #insertUpdate
  225. * @see #removeUpdate
  226. * @see #changedUpdate
  227. */
  228. /*protected*/ void forwardUpdate(DocumentEvent.ElementChange ec,
  229. DocumentEvent e, Shape a, ViewFactory f) {
  230. Element elem = getElement();
  231. int pos = e.getOffset();
  232. int index0 = getViewIndexAtPosition(pos);
  233. int index1 = index0;
  234. View v = (index0 >= 0) ? getView(index0) : null;
  235. if (v != null) {
  236. if ((v.getStartOffset() == pos) && (pos > 0)) {
  237. // If v is at a boundry, forward the event to the previous
  238. // view too.
  239. index0 = Math.max(index0 - 1, 0);
  240. }
  241. }
  242. if (e.getType() != DocumentEvent.EventType.REMOVE) {
  243. index1 = getViewIndexAtPosition(pos + e.getLength());
  244. if (index1 < 0) {
  245. index1 = getViewCount() - 1;
  246. }
  247. }
  248. int hole0 = index1 + 1;
  249. int hole1 = hole0;
  250. Element[] addedElems = (ec != null) ? ec.getChildrenAdded() : null;
  251. if ((addedElems != null) && (addedElems.length > 0)) {
  252. hole0 = ec.getIndex();
  253. hole1 = hole0 + addedElems.length - 1;
  254. }
  255. // forward to any view not in the forwarding hole
  256. // formed by added elements (i.e. they will be updated
  257. // by initialization.
  258. for (int i = index0; i <= index1; i++) {
  259. if (! ((i >= hole0) && (i <= hole1))) {
  260. v = getView(i);
  261. if (v != null) {
  262. Shape childAlloc = getChildAllocation(i, a);
  263. forwardUpdateToView(v, e, childAlloc, f);
  264. }
  265. }
  266. }
  267. }
  268. /**
  269. * Forward the DocumentEvent to the give child view. This
  270. * simply messages the view with a call to insertUpdate,
  271. * removeUpdate, or changedUpdate depending upon the type
  272. * of the event. This is called by
  273. * <a href="#forwardUpdate">forwardUpdate</a> to forward
  274. * the event to children that need it.
  275. *
  276. * @param v the child view to forward the event to.
  277. * @param e the change information from the associated document
  278. * @param a the current allocation of the view
  279. * @param f the factory to use to rebuild if the view has children
  280. * @see #forwardUpdate
  281. */
  282. /*protected*/ void forwardUpdateToView(View v, DocumentEvent e,
  283. Shape a, ViewFactory f) {
  284. DocumentEvent.EventType type = e.getType();
  285. if (type == DocumentEvent.EventType.INSERT) {
  286. v.insertUpdate(e, a, f);
  287. } else if (type == DocumentEvent.EventType.REMOVE) {
  288. v.removeUpdate(e, a, f);
  289. } else {
  290. v.changedUpdate(e, a, f);
  291. }
  292. }
  293. /**
  294. * Update the layout in response to receiving notification of
  295. * change from the model. This is implemented to call preferenceChanged
  296. * to reschedule a new layout if the ElementChange record is not null.
  297. *
  298. * @param ec changes to the element this view is responsible
  299. * for (may be null if there were no changes).
  300. * @param e the change information from the associated document
  301. * @param a the current allocation of the view
  302. * @param f the factory to use to rebuild if the view has children
  303. * @see #insertUpdate
  304. * @see #removeUpdate
  305. * @see #changedUpdate
  306. */
  307. /*protected*/ void updateLayout(DocumentEvent.ElementChange ec,
  308. DocumentEvent e, Shape a) {
  309. if ((ec != null) && (a != null)) {
  310. // should damage more intelligently
  311. preferenceChanged(null, true, true);
  312. getContainer().repaint();
  313. }
  314. }
  315. // --- View methods ---------------------------------------------
  316. /**
  317. * Sets the parent of the view.
  318. * This is reimplemented to provide the superclass
  319. * behavior as well as calling the <code>loadChildren</code>
  320. * method if this view does not already have children.
  321. * The children should not be loaded in the
  322. * constructor because the act of setting the parent
  323. * may cause them to try to search up the hierarchy
  324. * (to get the hosting Container for example).
  325. * If this view has children (the view is being moved
  326. * from one place in the view hierarchy to another),
  327. * the <code>loadChildren</code> method will not be called.
  328. *
  329. * @param parent the parent of the view, null if none
  330. */
  331. public void setParent(View parent) {
  332. super.setParent(parent);
  333. if ((parent != null) && (nchildren == 0)) {
  334. ViewFactory f = getViewFactory();
  335. loadChildren(f);
  336. }
  337. }
  338. /**
  339. * Returns the number of child views of this view.
  340. *
  341. * @return the number of views >= 0
  342. * @see #getView
  343. */
  344. public int getViewCount() {
  345. return nchildren;
  346. }
  347. /**
  348. * Gets the n-th view in this container.
  349. *
  350. * @param n the number of the view to get, >= 0 && < getViewCount()
  351. * @return the view
  352. */
  353. public View getView(int n) {
  354. return children[n];
  355. }
  356. /**
  357. * Fetches the allocation for the given child view.
  358. * This enables finding out where various views
  359. * are located.
  360. *
  361. * @param index the index of the child, >= 0 && < getViewCount()
  362. * @param a the allocation to this view.
  363. * @return the allocation to the child
  364. */
  365. public Shape getChildAllocation(int index, Shape a) {
  366. Rectangle alloc = getInsideAllocation(a);
  367. childAllocation(index, alloc);
  368. return alloc;
  369. }
  370. /**
  371. * Provides a mapping from the document model coordinate space
  372. * to the coordinate space of the view mapped to it.
  373. *
  374. * @param pos the position to convert >= 0
  375. * @param a the allocated region to render into
  376. * @return the bounding box of the given position
  377. * @exception BadLocationException if the given position does
  378. * not represent a valid location in the associated document
  379. * @see View#modelToView
  380. */
  381. public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
  382. boolean isBackward = (b == Position.Bias.Backward);
  383. int testPos = (isBackward) ? Math.max(0, pos - 1) : pos;
  384. if(isBackward && testPos < getStartOffset()) {
  385. return null;
  386. }
  387. int vIndex = getViewIndexAtPosition(testPos);
  388. if ((vIndex != -1) && (vIndex < getViewCount())) {
  389. View v = getView(vIndex);
  390. if(v != null && testPos >= v.getStartOffset() &&
  391. testPos < v.getEndOffset()) {
  392. Shape retShape = v.modelToView(pos, getChildAllocation(vIndex, a), b);
  393. if(retShape == null && v.getEndOffset() == pos) {
  394. if(++vIndex < getViewCount()) {
  395. v = getView(vIndex);
  396. retShape = v.modelToView(pos, getChildAllocation(vIndex, a), b);
  397. }
  398. }
  399. return retShape;
  400. }
  401. }
  402. throw new BadLocationException("Position not represented by view",
  403. pos);
  404. }
  405. /**
  406. * Provides a mapping from the document model coordinate space
  407. * to the coordinate space of the view mapped to it.
  408. *
  409. * @param p0 the position to convert >= 0
  410. * @param b0 the bias toward the previous character or the
  411. * next character represented by p0, in case the
  412. * position is a boundary of two views.
  413. * @param p1 the position to convert >= 0
  414. * @param b1 the bias toward the previous character or the
  415. * next character represented by p1, in case the
  416. * position is a boundary of two views.
  417. * @param a the allocated region to render into
  418. * @return the bounding box of the given position is returned
  419. * @exception BadLocationException if the given position does
  420. * not represent a valid location in the associated document
  421. * @exception IllegalArgumentException for an invalid bias argument
  422. * @see View#viewToModel
  423. */
  424. public Shape modelToView(int p0, Position.Bias b0, int p1, Position.Bias b1, Shape a) throws BadLocationException {
  425. if (p0 == getStartOffset() && p1 == getEndOffset()) {
  426. return a;
  427. }
  428. Rectangle alloc = getInsideAllocation(a);
  429. Rectangle r0 = new Rectangle(alloc);
  430. View v0 = getViewAtPosition((b0 == Position.Bias.Backward) ?
  431. Math.max(0, p0 - 1) : p0, r0);
  432. Rectangle r1 = new Rectangle(alloc);
  433. View v1 = getViewAtPosition((b1 == Position.Bias.Backward) ?
  434. Math.max(0, p1 - 1) : p1, r1);
  435. if (v0 == v1) {
  436. if (v0 == null) {
  437. return a;
  438. }
  439. // Range contained in one view
  440. return v0.modelToView(p0, b0, p1, b1, r0);
  441. }
  442. // Straddles some views.
  443. int viewCount = getViewCount();
  444. int counter = 0;
  445. while (counter < viewCount) {
  446. View v;
  447. // Views may not be in same order as model.
  448. // v0 or v1 may be null if there is a gap in the range this
  449. // view contains.
  450. if ((v = getView(counter)) == v0 || v == v1) {
  451. View endView;
  452. Rectangle retRect;
  453. Rectangle tempRect = new Rectangle();
  454. if (v == v0) {
  455. retRect = v0.modelToView(p0, b0, v0.getEndOffset(),
  456. Position.Bias.Backward, r0).
  457. getBounds();
  458. endView = v1;
  459. }
  460. else {
  461. retRect = v1.modelToView(v1.getStartOffset(),
  462. Position.Bias.Forward,
  463. p1, b1, r1).getBounds();
  464. endView = v0;
  465. }
  466. // Views entirely covered by range.
  467. while (++counter < viewCount &&
  468. (v = getView(counter)) != endView) {
  469. tempRect.setBounds(alloc);
  470. childAllocation(counter, tempRect);
  471. retRect.add(tempRect);
  472. }
  473. // End view.
  474. if (endView != null) {
  475. Shape endShape;
  476. if (endView == v1) {
  477. endShape = v1.modelToView(v1.getStartOffset(),
  478. Position.Bias.Forward,
  479. p1, b1, r1);
  480. }
  481. else {
  482. endShape = v0.modelToView(p0, b0, v0.getEndOffset(),
  483. Position.Bias.Backward, r0);
  484. }
  485. if (endShape instanceof Rectangle) {
  486. retRect.add((Rectangle)endShape);
  487. }
  488. else {
  489. retRect.add(endShape.getBounds());
  490. }
  491. }
  492. return retRect;
  493. }
  494. counter++;
  495. }
  496. throw new BadLocationException("Position not represented by view", p0);
  497. }
  498. /**
  499. * Provides a mapping from the view coordinate space to the logical
  500. * coordinate space of the model.
  501. *
  502. * @param x x coordinate of the view location to convert >= 0
  503. * @param y y coordinate of the view location to convert >= 0
  504. * @param a the allocated region to render into
  505. * @return the location within the model that best represents the
  506. * given point in the view >= 0
  507. * @see View#viewToModel
  508. */
  509. public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
  510. Rectangle alloc = getInsideAllocation(a);
  511. if (isBefore((int) x, (int) y, alloc)) {
  512. // point is before the range represented
  513. int retValue = -1;
  514. try {
  515. retValue = getNextVisualPositionFrom(-1, Position.Bias.Forward,
  516. a, EAST, bias);
  517. } catch (BadLocationException ble) { }
  518. catch (IllegalArgumentException iae) { }
  519. if(retValue == -1) {
  520. retValue = getStartOffset();
  521. bias[0] = Position.Bias.Forward;
  522. }
  523. return retValue;
  524. } else if (isAfter((int) x, (int) y, alloc)) {
  525. // point is after the range represented.
  526. int retValue = -1;
  527. try {
  528. retValue = getNextVisualPositionFrom(-1, Position.Bias.Forward,
  529. a, WEST, bias);
  530. } catch (BadLocationException ble) { }
  531. catch (IllegalArgumentException iae) { }
  532. if(retValue == -1) {
  533. // NOTE: this could actually use end offset with backward.
  534. retValue = getEndOffset() - 1;
  535. bias[0] = Position.Bias.Forward;
  536. }
  537. return retValue;
  538. } else {
  539. // locate the child and pass along the request
  540. View v = getViewAtPoint((int) x, (int) y, alloc);
  541. if (v != null) {
  542. return v.viewToModel(x, y, alloc, bias);
  543. }
  544. }
  545. return -1;
  546. }
  547. /**
  548. * Provides a way to determine the next visually represented model
  549. * location that one might place a caret. Some views may not be visible,
  550. * they might not be in the same order found in the model, or they just
  551. * might not allow access to some of the locations in the model.
  552. *
  553. * @param pos the position to convert >= 0
  554. * @param a the allocated region to render into
  555. * @param direction the direction from the current position that can
  556. * be thought of as the arrow keys typically found on a keyboard.
  557. * This may be SwingConstants.WEST, SwingConstants.EAST,
  558. * SwingConstants.NORTH, or SwingConstants.SOUTH.
  559. * @return the location within the model that best represents the next
  560. * location visual position.
  561. * @exception BadLocationException
  562. * @exception IllegalArgumentException for an invalid direction
  563. */
  564. public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a,
  565. int direction, Position.Bias[] biasRet)
  566. throws BadLocationException {
  567. Rectangle alloc = getInsideAllocation(a);
  568. switch (direction) {
  569. case NORTH:
  570. return getNextNorthSouthVisualPositionFrom(pos, b, a, direction,
  571. biasRet);
  572. case SOUTH:
  573. return getNextNorthSouthVisualPositionFrom(pos, b, a, direction,
  574. biasRet);
  575. case EAST:
  576. return getNextEastWestVisualPositionFrom(pos, b, a, direction,
  577. biasRet);
  578. case WEST:
  579. return getNextEastWestVisualPositionFrom(pos, b, a, direction,
  580. biasRet);
  581. default:
  582. throw new IllegalArgumentException("Bad direction: " + direction);
  583. }
  584. }
  585. /**
  586. * Gives notification that something was inserted into
  587. * the document in a location that this view is responsible for.
  588. * To reduce the burden to subclasses, this functionality is
  589. * spread out into the following calls that subclasses can
  590. * reimplement:
  591. * <ol>
  592. * <li><a href="#updateChildren">updateChildren</a> is called
  593. * if there were any changes to the element this view is
  594. * responsible for. If this view has child views that are
  595. * represent the child elements, then this method should do
  596. * whatever is necessary to make sure the child views correctly
  597. * represent the model.
  598. * <li><a href="#forwardUpdate">forwardUpdate</a> is called
  599. * to forward the DocumentEvent to the appropriate child views.
  600. * <li><a href="#updateLayout">updateLayout</a> is called to
  601. * give the view a chance to either repair it's layout, to reschedule
  602. * layout, or do nothing.
  603. * </ol>
  604. *
  605. * @param e the change information from the associated document
  606. * @param a the current allocation of the view
  607. * @param f the factory to use to rebuild if the view has children
  608. * @see View#insertUpdate
  609. */
  610. public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  611. Element elem = getElement();
  612. DocumentEvent.ElementChange ec = e.getChange(elem);
  613. if (ec != null) {
  614. if (! updateChildren(ec, e, f)) {
  615. // don't consider the element changes they
  616. // are for a view further down.
  617. ec = null;
  618. }
  619. }
  620. forwardUpdate(ec, e, a, f);
  621. updateLayout(ec, e, a);
  622. }
  623. /**
  624. * Gives notification that something was removed from the document
  625. * in a location that this view is responsible for.
  626. * To reduce the burden to subclasses, this functionality is
  627. * spread out into the following calls that subclasses can
  628. * reimplement:
  629. * <ol>
  630. * <li><a href="#updateChildren">updateChildren</a> is called
  631. * if there were any changes to the element this view is
  632. * responsible for. If this view has child views that are
  633. * represent the child elements, then this method should do
  634. * whatever is necessary to make sure the child views correctly
  635. * represent the model.
  636. * <li><a href="#forwardUpdate">forwardUpdate</a> is called
  637. * to forward the DocumentEvent to the appropriate child views.
  638. * <li><a href="#updateLayout">updateLayout</a> is called to
  639. * give the view a chance to either repair it's layout, to reschedule
  640. * layout, or do nothing.
  641. * </ol>
  642. *
  643. * @param e the change information from the associated document
  644. * @param a the current allocation of the view
  645. * @param f the factory to use to rebuild if the view has children
  646. * @see View#removeUpdate
  647. */
  648. public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  649. Element elem = getElement();
  650. DocumentEvent.ElementChange ec = e.getChange(elem);
  651. if (ec != null) {
  652. if (! updateChildren(ec, e, f)) {
  653. // don't consider the element changes they
  654. // are for a view further down.
  655. ec = null;
  656. }
  657. }
  658. forwardUpdate(ec, e, a, f);
  659. updateLayout(ec, e, a);
  660. }
  661. /**
  662. * Gives notification from the document that attributes were changed
  663. * in a location that this view is responsible for.
  664. * To reduce the burden to subclasses, this functionality is
  665. * spread out into the following calls that subclasses can
  666. * reimplement:
  667. * <ol>
  668. * <li><a href="#updateChildren">updateChildren</a> is called
  669. * if there were any changes to the element this view is
  670. * responsible for. If this view has child views that are
  671. * represent the child elements, then this method should do
  672. * whatever is necessary to make sure the child views correctly
  673. * represent the model.
  674. * <li><a href="#forwardUpdate">forwardUpdate</a> is called
  675. * to forward the DocumentEvent to the appropriate child views.
  676. * <li><a href="#updateLayout">updateLayout</a> is called to
  677. * give the view a chance to either repair it's layout, to reschedule
  678. * layout, or do nothing.
  679. * </ol>
  680. *
  681. * @param e the change information from the associated document
  682. * @param a the current allocation of the view
  683. * @param f the factory to use to rebuild if the view has children
  684. * @see View#changedUpdate
  685. */
  686. public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  687. Element elem = getElement();
  688. DocumentEvent.ElementChange ec = e.getChange(elem);
  689. if (ec != null) {
  690. if (! updateChildren(ec, e, f)) {
  691. // don't consider the element changes they
  692. // are for a view further down.
  693. ec = null;
  694. }
  695. }
  696. forwardUpdate(ec, e, a, f);
  697. updateLayout(ec, e, a);
  698. }
  699. // --- local methods ----------------------------------------------------
  700. /**
  701. * Tests whether a point lies before the rectangle range.
  702. *
  703. * @param x the X coordinate >= 0
  704. * @param y the Y coordinate >= 0
  705. * @param alloc the rectangle
  706. * @return true if the point is before the specified range
  707. */
  708. protected abstract boolean isBefore(int x, int y, Rectangle alloc);
  709. /**
  710. * Tests whether a point lies after the rectangle range.
  711. *
  712. * @param x the X coordinate >= 0
  713. * @param y the Y coordinate >= 0
  714. * @param alloc the rectangle
  715. * @return true if the point is after the specified range
  716. */
  717. protected abstract boolean isAfter(int x, int y, Rectangle alloc);
  718. /**
  719. * Fetches the child view at the given point.
  720. *
  721. * @param x the X coordinate >= 0
  722. * @param y the Y coordinate >= 0
  723. * @param alloc the parent's allocation on entry, which should
  724. * be changed to the child's allocation on exit
  725. * @return the child view
  726. */
  727. protected abstract View getViewAtPoint(int x, int y, Rectangle alloc);
  728. /**
  729. * Returns the allocation for a given child.
  730. *
  731. * @param index the index of the child, >= 0 && < getViewCount()
  732. * @param a the allocation to the interior of the box on entry,
  733. * and the allocation of the child view at the index on exit.
  734. */
  735. protected abstract void childAllocation(int index, Rectangle a);
  736. /**
  737. * Fetches the child view that represents the given position in
  738. * the model. This is implemented to fetch the view in the case
  739. * where there is a child view for each child element.
  740. *
  741. * @param pos the position >= 0
  742. * @param a the allocation to the interior of the box on entry,
  743. * and the allocation of the view containing the position on exit
  744. * @returns the view representing the given position, or
  745. * null if there isn't one
  746. */
  747. protected View getViewAtPosition(int pos, Rectangle a) {
  748. Element elem = getElement();
  749. int index = elem.getElementIndex(pos);
  750. Element child = elem.getElement(index);
  751. if ((child != null) && (index < getViewCount())) {
  752. View v = getView(index);
  753. if (v.getElement() == child) {
  754. if (a != null) {
  755. childAllocation(index, a);
  756. }
  757. return v;
  758. }
  759. }
  760. return null;
  761. }
  762. /**
  763. * Fetches the child view index representing the given position in
  764. * the model. This is implemented to fetch the view in the case
  765. * where there is a child view for each child element.
  766. *
  767. * @param pos the position >= 0
  768. * @returns index of the view representing the given position, or
  769. * -1 if no view represents that position
  770. */
  771. protected int getViewIndexAtPosition(int pos) {
  772. Element elem = getElement();
  773. return elem.getElementIndex(pos);
  774. }
  775. /**
  776. * Translates the immutable allocation given to the view
  777. * to a mutable allocation that represents the interior
  778. * allocation (i.e. the bounds of the given allocation
  779. * with the top, left, bottom, and right insets removed.
  780. * It is expected that the returned value would be further
  781. * mutated to represent an allocation to a child view.
  782. * This is implemented to reuse an instance variable so
  783. * it avoids creating excessive Rectangles. Typically
  784. * the result of calling this method would be fed to
  785. * the childAllocation method.
  786. *
  787. * @param a The allocation given to the view.
  788. * @returns The allocation that represents the inside of the
  789. * view after the margins have all been removed. If the
  790. * given allocation was null, the return value is null.
  791. */
  792. protected Rectangle getInsideAllocation(Shape a) {
  793. if (a != null) {
  794. // get the bounds, hopefully without allocating
  795. // a new rectangle. The Shape argument should
  796. // not be modified... we copy it into the
  797. // child allocation.
  798. Rectangle alloc;
  799. if (a instanceof Rectangle) {
  800. alloc = (Rectangle) a;
  801. } else {
  802. alloc = a.getBounds();
  803. }
  804. childAlloc.setBounds(alloc);
  805. childAlloc.x += left;
  806. childAlloc.y += top;
  807. childAlloc.width -= left + right;
  808. childAlloc.height -= top + bottom;
  809. return childAlloc;
  810. }
  811. return null;
  812. }
  813. /**
  814. * Sets the insets from the paragraph attributes specified in
  815. * the given attributes.
  816. *
  817. * @param attr the attributes
  818. */
  819. protected final void setParagraphInsets(AttributeSet attr) {
  820. // Since version 1.1 doesn't have scaling and assumes
  821. // a pixel is equal to a point, we just cast the point
  822. // sizes to integers.
  823. top = (short) StyleConstants.getSpaceAbove(attr);
  824. left = (short) StyleConstants.getLeftIndent(attr);
  825. bottom = (short) StyleConstants.getSpaceBelow(attr);
  826. right = (short) StyleConstants.getRightIndent(attr);
  827. }
  828. /**
  829. * Sets the insets for the view.
  830. *
  831. * @param top the top inset >= 0
  832. * @param left the left inset >= 0
  833. * @param bottom the bottom inset >= 0
  834. * @param right the right inset >= 0
  835. */
  836. protected final void setInsets(short top, short left, short bottom, short right) {
  837. this.top = top;
  838. this.left = left;
  839. this.right = right;
  840. this.bottom = bottom;
  841. }
  842. /**
  843. * Gets the left inset.
  844. *
  845. * @return the inset >= 0
  846. */
  847. protected final short getLeftInset() {
  848. return left;
  849. }
  850. /**
  851. * Gets the right inset.
  852. *
  853. * @return the inset >= 0
  854. */
  855. protected final short getRightInset() {
  856. return right;
  857. }
  858. /**
  859. * Gets the top inset.
  860. *
  861. * @return the inset >= 0
  862. */
  863. protected final short getTopInset() {
  864. return top;
  865. }
  866. /**
  867. * Gets the bottom inset.
  868. *
  869. * @return the inset >= 0
  870. */
  871. protected final short getBottomInset() {
  872. return bottom;
  873. }
  874. /**
  875. * Returns the next visual position for the cursor, in either the
  876. * east or west direction.
  877. *
  878. * @return next position west of the passed in position.
  879. */
  880. // PENDING: This only checks the next element. If one of the children
  881. // Views returns -1, it should continue checking all the children.
  882. // PENDING(sky): This name sucks! Come up with a better one!
  883. protected int getNextNorthSouthVisualPositionFrom(int pos, Position.Bias b,
  884. Shape a, int direction,
  885. Position.Bias[] biasRet)
  886. throws BadLocationException {
  887. if(getViewCount() == 0) {
  888. // Nothing to do.
  889. return pos;
  890. }
  891. boolean isNorth = (direction == NORTH);
  892. Rectangle alloc = getInsideAllocation(a);
  893. int retValue;
  894. if(pos == -1) {
  895. View v = (isNorth) ? getView(getViewCount() - 1) : getView(0);
  896. childAllocation(0, alloc);
  897. retValue = v.getNextVisualPositionFrom(pos, b, alloc, direction,
  898. biasRet);
  899. }
  900. else {
  901. int vIndex;
  902. if(b == Position.Bias.Backward && pos > 0) {
  903. vIndex = getViewIndexAtPosition(pos - 1);
  904. }
  905. else {
  906. vIndex = getViewIndexAtPosition(pos);
  907. }
  908. View v = getView(vIndex);
  909. childAllocation(vIndex, alloc);
  910. retValue = v.getNextVisualPositionFrom(pos, b, alloc, direction,
  911. biasRet);
  912. if(retValue == -1) {
  913. if((isNorth && --vIndex >= 0) ||
  914. (!isNorth && ++vIndex < getViewCount())) {
  915. v = getView(vIndex);
  916. alloc = getInsideAllocation(a);
  917. childAllocation(vIndex, alloc);
  918. retValue = v.getNextVisualPositionFrom(-1, b, alloc,
  919. direction, biasRet);
  920. }
  921. }
  922. }
  923. return retValue;
  924. }
  925. /**
  926. * Returns the next visual position for the cursor, in either the
  927. * east or west direction.
  928. *
  929. * @return next position west of the passed in position.
  930. */
  931. // PENDING: This only checks the next element. If one of the children
  932. // Views returns -1, it should continue checking all the children.
  933. // PENDING(sky): This name sucks! Come up with a better one!
  934. protected int getNextEastWestVisualPositionFrom(int pos, Position.Bias b,
  935. Shape a,
  936. int direction,
  937. Position.Bias[] biasRet)
  938. throws BadLocationException {
  939. boolean isEast = (direction == EAST);
  940. Rectangle alloc = getInsideAllocation(a);
  941. int retValue;
  942. int increment = (isEast) ? 1 : -1;
  943. if(pos == -1) {
  944. View v = (isEast) ? getView(0) : getView(getViewCount() - 1);
  945. childAllocation(0, alloc);
  946. retValue = v.getNextVisualPositionFrom(pos, b, alloc,
  947. direction, biasRet);
  948. if(retValue == -1 && isEast && getViewCount() > 1) {
  949. // Special case that should ONLY happen if first view
  950. // isn't valid (can happen when end position is put at
  951. // beginning of line.
  952. v = getView(1);
  953. alloc = getInsideAllocation(a);
  954. childAllocation(1, alloc);
  955. retValue = v.getNextVisualPositionFrom(-1, biasRet[0], alloc,
  956. direction, biasRet);
  957. }
  958. }
  959. else {
  960. int vIndex;
  961. if(b == Position.Bias.Backward) {
  962. vIndex = getViewIndexAtPosition(Math.max(getStartOffset(),
  963. pos - 1));
  964. }
  965. else {
  966. vIndex = getViewIndexAtPosition(pos);
  967. }
  968. View v = getView(vIndex);
  969. childAllocation(vIndex, alloc);
  970. retValue = v.getNextVisualPositionFrom(pos, b, alloc,
  971. direction, biasRet);
  972. if(retValue == -1) {
  973. if(flipEastAndWestAtEnds(pos, b)) {
  974. increment *= -1;
  975. }
  976. vIndex += increment;
  977. if(vIndex >= 0 && vIndex < getViewCount()) {
  978. v = getView(vIndex);
  979. alloc = getInsideAllocation(a);
  980. childAllocation(vIndex, alloc);
  981. retValue = v.getNextVisualPositionFrom
  982. (-1, b, alloc, direction, biasRet);
  983. // If there is a bias change, it is a fake position
  984. // and we should skip it. This is usually the result
  985. // of two elements side be side flowing the same way.
  986. if(retValue == pos && retValue != -1 && biasRet[0] != b) {
  987. alloc = getInsideAllocation(a);
  988. childAllocation(vIndex, alloc);
  989. retValue = v.getNextVisualPositionFrom
  990. (retValue, biasRet[0], alloc, direction,
  991. biasRet);
  992. }
  993. }
  994. }
  995. else {
  996. if(flipEastAndWestAtEnds(pos, b)) {
  997. increment *= -1;
  998. }
  999. vIndex += increment;
  1000. if(biasRet[0] != b &&
  1001. ((increment == 1 && v.getEndOffset() == retValue) ||
  1002. (increment == -1 && v.getStartOffset() == retValue)) &&
  1003. vIndex >= 0 && vIndex < getViewCount()) {
  1004. // Reached the end of a view, make sure the next view
  1005. // is a different direction.
  1006. v = getView(vIndex);
  1007. alloc = getInsideAllocation(a);
  1008. childAllocation(vIndex, alloc);
  1009. Position.Bias originalBias = biasRet[0];
  1010. int nextPos = v.getNextVisualPositionFrom
  1011. (-1, b, alloc, direction, biasRet);
  1012. if(biasRet[0] == b) {
  1013. retValue = nextPos;
  1014. }
  1015. else {
  1016. biasRet[0] = originalBias;
  1017. }
  1018. }
  1019. }
  1020. }
  1021. return retValue;
  1022. }
  1023. /**
  1024. * Subclasses may wish to subclass this and conditionally return
  1025. * true based on the position. A return value of true indicates that
  1026. * when a View returns -1 from getNextVisualPositionFrom the next
  1027. * view for east should be the current index offset by -1, and for
  1028. * west it means offset by 1. The normal direction (for left to
  1029. * right text) is to offset east by 1 and west by -1.
  1030. *
  1031. * @return false
  1032. */
  1033. protected boolean flipEastAndWestAtEnds(int position,
  1034. Position.Bias bias) {
  1035. return false;
  1036. }
  1037. // ---- member variables ---------------------------------------------
  1038. private static View[] ZERO = new View[0];
  1039. private View[] children;
  1040. private int nchildren;
  1041. private short left;
  1042. private short right;
  1043. private short top;
  1044. private short bottom;
  1045. private Rectangle childAlloc;
  1046. }