1. /*
  2. * @(#)AsyncBoxView.java 1.5 00/04/06
  3. *
  4. * Copyright 1998-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.awt.*;
  12. import javax.swing.SwingUtilities;
  13. import javax.swing.event.DocumentEvent;
  14. /**
  15. * A box that does layout asynchronously. This
  16. * is useful to keep the GUI event thread moving by
  17. * not doing any layout on it. The layout is done
  18. * on a granularity of operations on the child views.
  19. * After each child view is accessed for some part
  20. * of layout (a potentially time consuming operation)
  21. * the remaining tasks can be abandoned or a new higher
  22. * priority task (i.e. to service a synchronous request
  23. * or a visible area) can be taken on.
  24. * <p>
  25. * While the child view is being accessed
  26. * a read lock is aquired on the associated document
  27. * so that the model is stable while being accessed.
  28. *
  29. * @author Timothy Prinzing
  30. * @version 1.5 04/06/00
  31. * @since 1.3
  32. */
  33. public class AsyncBoxView extends View {
  34. /**
  35. * Construct a box view that does asynchronous layout.
  36. *
  37. * @param elem the element of the model to represent
  38. * @param axis the axis to tile along. This can be
  39. * either X_AXIS or Y_AXIS.
  40. */
  41. public AsyncBoxView(Element elem, int axis) {
  42. super(elem);
  43. stats = new ChildVector();
  44. this.axis = axis;
  45. locator = new ChildLocator();
  46. flushTask = new FlushTask();
  47. minorSpan = Short.MAX_VALUE;
  48. }
  49. /**
  50. * Fetch the major axis (the axis the children
  51. * are tiled along). This will have a value of
  52. * either X_AXIS or Y_AXIS.
  53. */
  54. public int getMajorAxis() {
  55. return axis;
  56. }
  57. /**
  58. * Fetch the minor axis (the axis orthoginal
  59. * to the tiled axis). This will have a value of
  60. * either X_AXIS or Y_AXIS.
  61. */
  62. public int getMinorAxis() {
  63. return (axis == X_AXIS) ? Y_AXIS : X_AXIS;
  64. }
  65. /**
  66. * Get the top part of the margin around the view.
  67. */
  68. public float getTopInset() {
  69. return topInset;
  70. }
  71. /**
  72. * Set the top part of the margin around the view.
  73. *
  74. * @param i the value of the inset
  75. */
  76. public void setTopInset(float i) {
  77. topInset = i;
  78. }
  79. /**
  80. * Get the bottom part of the margin around the view.
  81. */
  82. public float getBottomInset() {
  83. return bottomInset;
  84. }
  85. /**
  86. * Set the bottom part of the margin around the view.
  87. *
  88. * @param i the value of the inset
  89. */
  90. public void setBottomInset(float i) {
  91. bottomInset = i;
  92. }
  93. /**
  94. * Get the left part of the margin around the view.
  95. */
  96. public float getLeftInset() {
  97. return leftInset;
  98. }
  99. /**
  100. * Set the left part of the margin around the view.
  101. *
  102. * @param i the value of the inset
  103. */
  104. public void setLeftInset(float i) {
  105. leftInset = i;
  106. }
  107. /**
  108. * Get the right part of the margin around the view.
  109. */
  110. public float getRightInset() {
  111. return rightInset;
  112. }
  113. /**
  114. * Set the right part of the margin around the view.
  115. *
  116. * @param i the value of the inset
  117. */
  118. public void setRightInset(float i) {
  119. rightInset = i;
  120. }
  121. /**
  122. * Fetch the object representing the layout state of
  123. * of the child at the given index.
  124. *
  125. * @param index the child index. This should be a
  126. * value >= 0 and < getViewCount().
  127. */
  128. protected ChildState getChildState(int index) {
  129. synchronized(stats) {
  130. return stats.getChildState(index);
  131. }
  132. }
  133. /**
  134. * Fetch the queue to use for layout.
  135. */
  136. protected LayoutQueue getLayoutQueue() {
  137. return LayoutQueue.getDefaultQueue();
  138. }
  139. /**
  140. * New ChildState records are created through
  141. * this method to allow subclasses the extend
  142. * the ChildState records to do/hold more
  143. */
  144. protected ChildState createChildState(View v) {
  145. return new ChildState(v);
  146. }
  147. /**
  148. * Requirements changed along the major axis.
  149. * This is called by the thread doing layout for
  150. * the given ChildState object when it has completed
  151. * fetching the child views new preferences.
  152. * Typically this would be the layout thread, but
  153. * might be the GUI thread if it is trying to update
  154. * something immediately (such as to perform a
  155. * model/view translation).
  156. */
  157. protected synchronized void majorRequirementChange(ChildState cs, float delta) {
  158. majorSpan += delta;
  159. majorChanged = true;
  160. }
  161. /**
  162. * Requirements changed along the minor axis.
  163. * This is called by the thread doing layout for
  164. * the given ChildState object when it has completed
  165. * fetching the child views new preferences.
  166. * Typically this would be the layout thread, but
  167. * might be the GUI thread if it is trying to update
  168. * something immediately (such as to perform a
  169. * model/view translation).
  170. */
  171. protected synchronized void minorRequirementChange(ChildState cs) {
  172. minorChanged = true;
  173. }
  174. /**
  175. * Publish the changes in preferences upward to the parent
  176. * view. This is called by the layout thread.
  177. */
  178. protected synchronized void flushRequirementChanges() {
  179. if (majorChanged || minorChanged) {
  180. View p = getParent();
  181. if (p != null) {
  182. boolean horizontal;
  183. boolean vertical;
  184. if (axis == X_AXIS) {
  185. horizontal = majorChanged;
  186. vertical = minorChanged;
  187. } else {
  188. vertical = majorChanged;
  189. horizontal = minorChanged;
  190. }
  191. // propagate a preferenceChanged, using the
  192. // layout thread.
  193. p.preferenceChanged(this, horizontal, vertical);
  194. majorChanged = false;
  195. minorChanged = false;
  196. // probably want to change this to be more exact.
  197. Component c = getContainer();
  198. if (c != null) {
  199. c.repaint();
  200. }
  201. }
  202. }
  203. }
  204. /**
  205. * Calls the superclass to update the child views, and
  206. * updates the status records for the children. This
  207. * is expected to be called while a write lock is held
  208. * on the model so that interaction with the layout
  209. * thread will not happen (i.e. the layout thread
  210. * acquires a read lock before doing anything).
  211. *
  212. * @param offset the starting offset into the child views >= 0
  213. * @param length the number of existing views to replace >= 0
  214. * @param views the child views to insert
  215. */
  216. public void replace(int offset, int length, View[] views) {
  217. synchronized(stats) {
  218. LayoutQueue q = getLayoutQueue();
  219. ChildState[] s = new ChildState[views.length];
  220. for (int i = 0; i < s.length; i++) {
  221. s[i] = createChildState(views[i]);
  222. }
  223. stats.replace(offset, length, s);
  224. // tasks must be added after the above loop so that
  225. // when they start executing the child state vector
  226. // will have the records. Unfortunately, this means
  227. // two trips through the array.
  228. if (s.length != 0) {
  229. for (int i = 0; i < s.length; i++) {
  230. q.addTask(s[i]);
  231. }
  232. q.addTask(flushTask);
  233. }
  234. }
  235. }
  236. /**
  237. * Loads all of the children to initialize the view.
  238. * This is called by the <a href="#setParent">setParent</a>
  239. * method. Subclasses can reimplement this to initialize
  240. * their child views in a different manner. The default
  241. * implementation creates a child view for each
  242. * child element.
  243. * <p>
  244. * Normally a write-lock is held on the Document while
  245. * the children are being changed, which keeps the rendering
  246. * and layout threads safe. The exception to this is when
  247. * the view is initialized to represent an existing element
  248. * (via this method), so it is synchronized to exclude
  249. * preferenceChanged while we are initializing.
  250. *
  251. * @param f the view factory
  252. * @see #setParent
  253. */
  254. protected void loadChildren(ViewFactory f) {
  255. Element e = getElement();
  256. int n = e.getElementCount();
  257. if (n > 0) {
  258. View[] added = new View[n];
  259. for (int i = 0; i < n; i++) {
  260. added[i] = f.create(e.getElement(i));
  261. }
  262. replace(0, 0, added);
  263. }
  264. }
  265. /**
  266. * Fetches the child view index representing the given position in
  267. * the model. This is implemented to fetch the view in the case
  268. * where there is a child view for each child element.
  269. *
  270. * @param pos the position >= 0
  271. * @returns index of the view representing the given position, or
  272. * -1 if no view represents that position
  273. */
  274. protected synchronized int getViewIndexAtPosition(int pos, Position.Bias b) {
  275. boolean isBackward = (b == Position.Bias.Backward);
  276. pos = (isBackward) ? Math.max(0, pos - 1) : pos;
  277. Element elem = getElement();
  278. return elem.getElementIndex(pos);
  279. }
  280. /**
  281. * Update the layout in response to receiving notification of
  282. * change from the model. This is implemented to note the
  283. * change on the ChildLocator so that offsets of the children
  284. * will be correctly computed.
  285. *
  286. * @param ec changes to the element this view is responsible
  287. * for (may be null if there were no changes).
  288. * @param e the change information from the associated document
  289. * @param a the current allocation of the view
  290. * @param f the factory to use to rebuild if the view has children
  291. * @see #insertUpdate
  292. * @see #removeUpdate
  293. * @see #changedUpdate
  294. */
  295. protected void updateLayout(DocumentEvent.ElementChange ec,
  296. DocumentEvent e, Shape a) {
  297. if (ec != null) {
  298. // the newly inserted children don't have a valid
  299. // offset so the child locator needs to be messaged
  300. // that the child prior to the new children has
  301. // changed size.
  302. int index = Math.max(ec.getIndex() - 1, 0);
  303. ChildState cs = getChildState(index);
  304. locator.childChanged(cs);
  305. }
  306. }
  307. // --- View methods ------------------------------------
  308. /**
  309. * Sets the parent of the view.
  310. * This is reimplemented to provide the superclass
  311. * behavior as well as calling the <code>loadChildren</code>
  312. * method if this view does not already have children.
  313. * The children should not be loaded in the
  314. * constructor because the act of setting the parent
  315. * may cause them to try to search up the hierarchy
  316. * (to get the hosting Container for example).
  317. * If this view has children (the view is being moved
  318. * from one place in the view hierarchy to another),
  319. * the <code>loadChildren</code> method will not be called.
  320. *
  321. * @param parent the parent of the view, null if none
  322. */
  323. public void setParent(View parent) {
  324. super.setParent(parent);
  325. if ((parent != null) && (getViewCount() == 0)) {
  326. ViewFactory f = getViewFactory();
  327. loadChildren(f);
  328. }
  329. }
  330. /**
  331. * Child views can call this on the parent to indicate that
  332. * the preference has changed and should be reconsidered
  333. * for layout. This is reimplemented to queue new work
  334. * on the layout thread. This method gets messaged from
  335. * multiple threads via the children.
  336. *
  337. * @param child the child view
  338. * @param width true if the width preference has changed
  339. * @param height true if the height preference has changed
  340. * @see javax.swing.JComponent#revalidate
  341. */
  342. public synchronized void preferenceChanged(View child, boolean width, boolean height) {
  343. if (child == null) {
  344. getParent().preferenceChanged(this, width, height);
  345. } else {
  346. if (changing != null) {
  347. View cv = changing.getChildView();
  348. if (cv == child) {
  349. // size was being changed on the child, no need to
  350. // queue work for it.
  351. changing.preferenceChanged(width, height);
  352. return;
  353. }
  354. }
  355. int index = getViewIndexAtPosition(child.getStartOffset(),
  356. Position.Bias.Forward);
  357. ChildState cs = getChildState(index);
  358. cs.preferenceChanged(width, height);
  359. LayoutQueue q = getLayoutQueue();
  360. q.addTask(cs);
  361. q.addTask(flushTask);
  362. }
  363. }
  364. /**
  365. * Sets the size of the view. This should cause
  366. * layout of the view, if it has any layout duties.
  367. * <p>
  368. * This is implemented to check and see if there has
  369. * been a change in the minor span (since the view
  370. * is flexible along the minor axis). If there has
  371. * been a change, this will add a high priorty task
  372. * on the layout thread that will mark all of the
  373. * ChildState records as needing to resize the child,
  374. * and to spawn a bunch of low priority tasks to
  375. * fixup the children.
  376. * <p>
  377. * This method will normally be called by the
  378. * GUI event thread, which we don't want to slow
  379. * down in any way if we can help it. Pushing the
  380. * potentially time consuming task of marking each
  381. * record frees the GUI thread, but also leaves the
  382. * view open to paint attempts that can't be satisfied.
  383. * The view is marked as <em>resizing</em> and the
  384. * ResizeTask will turn off the flag when the children
  385. * have all been marked.
  386. *
  387. * @param width the width >= 0
  388. * @param height the height >= 0
  389. */
  390. public void setSize(float width, float height) {
  391. float targetSpan;
  392. if (axis == X_AXIS) {
  393. targetSpan = height - getTopInset() - getBottomInset();
  394. } else {
  395. targetSpan = width - getLeftInset() - getRightInset();
  396. }
  397. if (targetSpan != minorSpan) {
  398. minorSpan = targetSpan;
  399. // mark all of the ChildState instances as needing to
  400. // resize the child, and queue up work to fix them.
  401. int n = getViewCount();
  402. LayoutQueue q = getLayoutQueue();
  403. for (int i = 0; i < n; i++) {
  404. ChildState cs = getChildState(i);
  405. cs.childSizeValid = false;
  406. q.addTask(cs);
  407. }
  408. q.addTask(flushTask);
  409. }
  410. }
  411. /**
  412. * Render the view using the given allocation and
  413. * rendering surface.
  414. * <p>
  415. * This is implemented to determine whether or not the
  416. * desired region to be rendered (i.e. the unclipped
  417. * area) is up to date or not. If up-to-date the children
  418. * are rendered. If not up-to-date, a task to build
  419. * the desired area is placed on the layout queue as
  420. * a high priority task. This keeps by event thread
  421. * moving by rendering if ready, and postponing until
  422. * a later time if not ready (since paint requests
  423. * can be rescheduled).
  424. *
  425. * @param g the rendering surface to use
  426. * @param alloc the allocated region to render into
  427. * @see View#paint
  428. */
  429. public void paint(Graphics g, Shape alloc) {
  430. synchronized (locator) {
  431. locator.setAllocation(alloc);
  432. locator.paintChildren(g);
  433. }
  434. }
  435. /**
  436. * Determines the preferred span for this view along an
  437. * axis.
  438. *
  439. * @param axis may be either View.X_AXIS or View.Y_AXIS
  440. * @returns the span the view would like to be rendered into >= 0.
  441. * Typically the view is told to render into the span
  442. * that is returned, although there is no guarantee.
  443. * The parent may choose to resize or break the view.
  444. * @exception IllegalArgumentException for an invalid axis type
  445. */
  446. public float getPreferredSpan(int axis) {
  447. if (axis == this.axis) {
  448. return majorSpan;
  449. }
  450. if (prefRequest != null) {
  451. View child = prefRequest.getChildView();
  452. return child.getPreferredSpan(axis);
  453. }
  454. // nothing is known about the children yet
  455. if (axis == X_AXIS) {
  456. return getLeftInset() + getRightInset() + 30;
  457. } else {
  458. return getTopInset() + getBottomInset() + 30;
  459. }
  460. }
  461. /**
  462. * Determines the minimum span for this view along an
  463. * axis.
  464. *
  465. * @param axis may be either View.X_AXIS or View.Y_AXIS
  466. * @returns the span the view would like to be rendered into >= 0.
  467. * Typically the view is told to render into the span
  468. * that is returned, although there is no guarantee.
  469. * The parent may choose to resize or break the view.
  470. * @exception IllegalArgumentException for an invalid axis type
  471. */
  472. public float getMinimumSpan(int axis) {
  473. if (axis == this.axis) {
  474. return getPreferredSpan(axis);
  475. }
  476. if (minRequest != null) {
  477. View child = minRequest.getChildView();
  478. return child.getMinimumSpan(axis);
  479. }
  480. // nothing is known about the children yet
  481. if (axis == X_AXIS) {
  482. return getLeftInset() + getRightInset() + 5;
  483. } else {
  484. return getTopInset() + getBottomInset() + 5;
  485. }
  486. }
  487. /**
  488. * Determines the maximum span for this view along an
  489. * axis.
  490. *
  491. * @param axis may be either View.X_AXIS or View.Y_AXIS
  492. * @returns the span the view would like to be rendered into >= 0.
  493. * Typically the view is told to render into the span
  494. * that is returned, although there is no guarantee.
  495. * The parent may choose to resize or break the view.
  496. * @exception IllegalArgumentException for an invalid axis type
  497. */
  498. public float getMaximumSpan(int axis) {
  499. if (axis == this.axis) {
  500. return getPreferredSpan(axis);
  501. }
  502. return Short.MAX_VALUE;
  503. }
  504. /**
  505. * Returns the number of views in this view. Since
  506. * the default is to not be a composite view this
  507. * returns 0.
  508. *
  509. * @return the number of views >= 0
  510. * @see View#getViewCount
  511. */
  512. public int getViewCount() {
  513. synchronized(stats) {
  514. return stats.size();
  515. }
  516. }
  517. /**
  518. * Gets the nth child view. Since there are no
  519. * children by default, this returns null.
  520. *
  521. * @param n the number of the view to get, >= 0 && < getViewCount()
  522. * @return the view
  523. */
  524. public View getView(int n) {
  525. synchronized(stats) {
  526. if ((n >= 0) && (n < stats.size())) {
  527. ChildState cs = stats.getChildState(n);
  528. return cs.getChildView();
  529. }
  530. }
  531. return null;
  532. }
  533. /**
  534. * Fetches the allocation for the given child view.
  535. * This enables finding out where various views
  536. * are located, without assuming the views store
  537. * their location. This returns null since the
  538. * default is to not have any child views.
  539. *
  540. * @param index the index of the child, >= 0 && < getViewCount()
  541. * @param a the allocation to this view.
  542. * @return the allocation to the child
  543. */
  544. public Shape getChildAllocation(int index, Shape a) {
  545. Shape ca = locator.getChildAllocation(index, a);
  546. return ca;
  547. }
  548. /**
  549. * Returns the child view index representing the given position in
  550. * the model. By default a view has no children so this is implemented
  551. * to return -1 to indicate there is no valid child index for any
  552. * position.
  553. *
  554. * @param pos the position >= 0
  555. * @return index of the view representing the given position, or
  556. * -1 if no view represents that position
  557. * @since 1.3
  558. */
  559. public int getViewIndex(int pos, Position.Bias b) {
  560. return getViewIndexAtPosition(pos, b);
  561. }
  562. /**
  563. * Provides a mapping from the document model coordinate space
  564. * to the coordinate space of the view mapped to it.
  565. *
  566. * @param pos the position to convert >= 0
  567. * @param a the allocated region to render into
  568. * @param b the bias toward the previous character or the
  569. * next character represented by the offset, in case the
  570. * position is a boundary of two views.
  571. * @return the bounding box of the given position is returned
  572. * @exception BadLocationException if the given position does
  573. * not represent a valid location in the associated document
  574. * @exception IllegalArgumentException for an invalid bias argument
  575. * @see View#viewToModel
  576. */
  577. public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
  578. int index = getViewIndexAtPosition(pos, b);
  579. Shape ca = locator.getChildAllocation(index, a);
  580. // forward to the child view, and make sure we don't
  581. // interact with the layout thread by synchronizing
  582. // on the child state.
  583. ChildState cs = getChildState(index);
  584. synchronized (cs) {
  585. View cv = cs.getChildView();
  586. Shape v = cv.modelToView(pos, ca, b);
  587. return v;
  588. }
  589. }
  590. /**
  591. * Provides a mapping from the view coordinate space to the logical
  592. * coordinate space of the model. The biasReturn argument will be
  593. * filled in to indicate that the point given is closer to the next
  594. * character in the model or the previous character in the model.
  595. * <p>
  596. * This is expected to be called by the GUI thread, holding a
  597. * read-lock on the associated model. It is implemented to
  598. * locate the child view and determine it's allocation with a
  599. * lock on the ChildLocator object, and to call viewToModel
  600. * on the child view with a lock on the ChildState object
  601. * to avoid interaction with the layout thread.
  602. *
  603. * @param x the X coordinate >= 0
  604. * @param y the Y coordinate >= 0
  605. * @param a the allocated region to render into
  606. * @return the location within the model that best represents the
  607. * given point in the view >= 0. The biasReturn argument will be
  608. * filled in to indicate that the point given is closer to the next
  609. * character in the model or the previous character in the model.
  610. */
  611. public int viewToModel(float x, float y, Shape a, Position.Bias[] biasReturn) {
  612. int pos; // return position
  613. int index; // child index to forward to
  614. Shape ca; // child allocation
  615. // locate the child view and it's allocation so that
  616. // we can forward to it. Make sure the layout thread
  617. // doesn't change anything by trying to flush changes
  618. // to the parent while the GUI thread is trying to
  619. // find the child and it's allocation.
  620. synchronized (locator) {
  621. index = locator.getViewIndexAtPoint(x, y, a);
  622. ca = locator.getChildAllocation(index, a);
  623. }
  624. // forward to the child view, and make sure we don't
  625. // interact with the layout thread by synchronizing
  626. // on the child state.
  627. ChildState cs = getChildState(index);
  628. synchronized (cs) {
  629. View v = cs.getChildView();
  630. pos = v.viewToModel(x, y, ca, biasReturn);
  631. }
  632. return pos;
  633. }
  634. // --- variables -----------------------------------------
  635. /**
  636. * The major axis against which the children are
  637. * tiled.
  638. */
  639. int axis;
  640. /**
  641. * The children and their layout statistics.
  642. */
  643. ChildVector stats;
  644. /**
  645. * Current span along the major axis. This
  646. * is also the value returned by getMinimumSize,
  647. * getPreferredSize, and getMaximumSize along
  648. * the major axis.
  649. */
  650. float majorSpan;
  651. /**
  652. * Current span along the minor axis. This
  653. * is what layout was done against (i.e. things
  654. * are flexible in this direction).
  655. */
  656. float minorSpan;
  657. /**
  658. * Object that manages the offsets of the
  659. * children. All locking for management of
  660. * child locations is on this object.
  661. */
  662. protected ChildLocator locator;
  663. float topInset;
  664. float bottomInset;
  665. float leftInset;
  666. float rightInset;
  667. ChildState minRequest;
  668. ChildState prefRequest;
  669. boolean majorChanged;
  670. boolean minorChanged;
  671. Runnable flushTask;
  672. /**
  673. * Child that is actively changing size. This often
  674. * causes a preferenceChanged, so this is a cache to
  675. * possibly speed up the marking the state. It also
  676. * helps flag an opportunity to avoid adding to flush
  677. * task to the layout queue.
  678. */
  679. ChildState changing;
  680. /**
  681. * A class to manage the effective position of the
  682. * child views in a localized area while changes are
  683. * being made around the localized area. The AsyncBoxView
  684. * may be continuously changing, but the visible area
  685. * needs to remain fairly stable until the layout thread
  686. * decides to publish an update to the parent.
  687. */
  688. public class ChildLocator {
  689. /**
  690. * construct a child locator.
  691. */
  692. public ChildLocator() {
  693. lastAlloc = new Rectangle();
  694. childAlloc = new Rectangle();
  695. }
  696. /**
  697. * Notification that a child changed. This can effect
  698. * whether or not new offset calculations are needed.
  699. * This is called by a ChildState object that has
  700. * changed it's major span. This can therefore be
  701. * called by multiple threads.
  702. */
  703. public synchronized void childChanged(ChildState cs) {
  704. if (lastValidOffset == null) {
  705. lastValidOffset = cs;
  706. } else if (cs.getChildView().getStartOffset() <
  707. lastValidOffset.getChildView().getStartOffset()) {
  708. lastValidOffset = cs;
  709. }
  710. }
  711. /**
  712. * Paint the children that intersect the clip area.
  713. */
  714. public synchronized void paintChildren(Graphics g) {
  715. Rectangle clip = g.getClipBounds();
  716. float targetOffset = (axis == X_AXIS) ?
  717. clip.x - lastAlloc.x : clip.y - lastAlloc.y;
  718. int index = getViewIndexAtVisualOffset(targetOffset);
  719. int n = getViewCount();
  720. float offs = getChildState(index).getMajorOffset();
  721. for (int i = index; i < n; i++) {
  722. ChildState cs = getChildState(i);
  723. cs.setMajorOffset(offs);
  724. Shape ca = getChildAllocation(i);
  725. if (intersectsClip(ca, clip)) {
  726. synchronized (cs) {
  727. View v = cs.getChildView();
  728. v.paint(g, ca);
  729. }
  730. } else {
  731. // done painting intersection
  732. break;
  733. }
  734. offs += cs.getMajorSpan();
  735. }
  736. }
  737. /**
  738. * Fetch the allocation to use for a child view.
  739. * This will update the offsets for all children
  740. * not yet updated before the given index.
  741. */
  742. public synchronized Shape getChildAllocation(int index, Shape a) {
  743. if (a == null) {
  744. return null;
  745. }
  746. setAllocation(a);
  747. ChildState cs = getChildState(index);
  748. if (cs.getChildView().getStartOffset() >
  749. lastValidOffset.getChildView().getStartOffset()) {
  750. // offsets need to be updated
  751. updateChildOffsetsToIndex(index);
  752. }
  753. Shape ca = getChildAllocation(index);
  754. return ca;
  755. }
  756. /**
  757. * Fetches the child view index at the given point.
  758. * This is called by the various View methods that
  759. * need to calculate which child to forward a message
  760. * to. This should be called by a block synchronized
  761. * on this object, and would typically be followed
  762. * with one or more calls to getChildAllocation that
  763. * should also be in the synchronized block.
  764. *
  765. * @param x the X coordinate >= 0
  766. * @param y the Y coordinate >= 0
  767. * @param a the allocation to the View
  768. * @return the nearest child index
  769. */
  770. public int getViewIndexAtPoint(float x, float y, Shape a) {
  771. setAllocation(a);
  772. float targetOffset = (axis == X_AXIS) ? x - lastAlloc.x : y - lastAlloc.y;
  773. int index = getViewIndexAtVisualOffset(targetOffset);
  774. return index;
  775. }
  776. /**
  777. * Fetch the allocation to use for a child view.
  778. * <em>This does not update the offsets in the ChildState
  779. * records.</em>
  780. */
  781. protected Shape getChildAllocation(int index) {
  782. ChildState cs = getChildState(index);
  783. if (! cs.isLayoutValid()) {
  784. cs.run();
  785. }
  786. if (axis == X_AXIS) {
  787. childAlloc.x = lastAlloc.x + (int) cs.getMajorOffset();
  788. childAlloc.y = lastAlloc.y + (int) cs.getMinorOffset();
  789. childAlloc.width = (int) cs.getMajorSpan();
  790. childAlloc.height = (int) cs.getMinorSpan();
  791. } else {
  792. childAlloc.y = lastAlloc.y + (int) cs.getMajorOffset();
  793. childAlloc.x = lastAlloc.x + (int) cs.getMinorOffset();
  794. childAlloc.height = (int) cs.getMajorSpan();
  795. childAlloc.width = (int) cs.getMinorSpan();
  796. }
  797. return childAlloc;
  798. }
  799. /**
  800. * Copy the currently allocated shape into the Rectangle
  801. * used to store the current allocation. This would be
  802. * a floating point rectangle in a Java2D-specific implmentation.
  803. */
  804. protected void setAllocation(Shape a) {
  805. if (a instanceof Rectangle) {
  806. lastAlloc.setBounds((Rectangle) a);
  807. } else {
  808. lastAlloc.setBounds(a.getBounds());
  809. }
  810. setSize(lastAlloc.width, lastAlloc.height);
  811. }
  812. /**
  813. * Locate the view responsible for an offset into the box
  814. * along the major axis. Make sure that offsets are set
  815. * on the ChildState objects up to the given target span
  816. * past the desired offset.
  817. *
  818. * @returns index of the view representing the given visual
  819. * location (targetOffset), or -1 if no view represents
  820. * that location.
  821. */
  822. protected int getViewIndexAtVisualOffset(float targetOffset) {
  823. int n = getViewCount();
  824. if (n > 0) {
  825. if (lastValidOffset == null) {
  826. lastValidOffset = getChildState(0);
  827. }
  828. if (targetOffset > majorSpan) {
  829. // should only get here on the first time display.
  830. return 0;
  831. } else if (targetOffset > lastValidOffset.getMajorOffset()) {
  832. // roll offset calculations forward
  833. return updateChildOffsets(targetOffset);
  834. } else {
  835. // no changes prior to the needed offset
  836. // this should be a binary search
  837. float offs = 0f;
  838. for (int i = 0; i < n; i++) {
  839. ChildState cs = getChildState(i);
  840. float nextOffs = offs + cs.getMajorSpan();
  841. if (targetOffset < nextOffs) {
  842. return i;
  843. }
  844. offs = nextOffs;
  845. }
  846. }
  847. }
  848. return n - 1;
  849. }
  850. /**
  851. * Move the location of the last offset calculation forward
  852. * to the desired offset.
  853. */
  854. int updateChildOffsets(float targetOffset) {
  855. int n = getViewCount();
  856. int targetIndex = n - 1;;
  857. int pos = lastValidOffset.getChildView().getStartOffset();
  858. int startIndex = getViewIndexAtPosition(pos, Position.Bias.Forward);
  859. float start = lastValidOffset.getMajorOffset();
  860. float lastOffset = start;
  861. for (int i = startIndex; i < n; i++) {
  862. ChildState cs = getChildState(i);
  863. cs.setMajorOffset(lastOffset);
  864. lastOffset += cs.getMajorSpan();
  865. if (targetOffset < lastOffset) {
  866. targetIndex = i;
  867. lastValidOffset = cs;
  868. break;
  869. }
  870. }
  871. return targetIndex;
  872. }
  873. /**
  874. * Move the location of the last offset calculation forward
  875. * to the desired index.
  876. */
  877. void updateChildOffsetsToIndex(int index) {
  878. int pos = lastValidOffset.getChildView().getStartOffset();
  879. int startIndex = getViewIndexAtPosition(pos, Position.Bias.Forward);
  880. float lastOffset = lastValidOffset.getMajorOffset();
  881. for (int i = startIndex; i <= index; i++) {
  882. ChildState cs = getChildState(i);
  883. cs.setMajorOffset(lastOffset);
  884. lastOffset += cs.getMajorSpan();
  885. }
  886. }
  887. boolean intersectsClip(Shape childAlloc, Rectangle clip) {
  888. Rectangle cs = (childAlloc instanceof Rectangle) ?
  889. (Rectangle) childAlloc : childAlloc.getBounds();
  890. return cs.intersects(clip);
  891. }
  892. /**
  893. * The location of the last offset calculation
  894. * that is valid.
  895. */
  896. protected ChildState lastValidOffset;
  897. /**
  898. * The last seen allocation (for repainting when changes
  899. * are flushed upward).
  900. */
  901. protected Rectangle lastAlloc;
  902. /**
  903. * A shape to use for the child allocation to avoid
  904. * creating a lot of garbage.
  905. */
  906. protected Rectangle childAlloc;
  907. }
  908. /**
  909. * A record representing the layout state of a
  910. * child view. It is runnable as a task on another
  911. * thread. All access to the child view that is
  912. * based upon a read-lock on the model should synchronize
  913. * on this object (i.e. The layout thread and the GUI
  914. * thread can both have a read lock on the model at the
  915. * same time and are not protected from each other).
  916. * Access to a child view hierarchy is serialized via
  917. * synchronization on the ChildState instance.
  918. */
  919. public class ChildState implements Runnable {
  920. /**
  921. * Construct a child status. This needs to start
  922. * out as fairly large so we don't falsely begin with
  923. * the idea that all of the children are visible.
  924. */
  925. public ChildState(View v) {
  926. child = v;
  927. minorValid = false;
  928. majorValid = false;
  929. childSizeValid = false;
  930. child.setParent(AsyncBoxView.this);
  931. }
  932. /**
  933. * Fetch the child view this record represents
  934. */
  935. public View getChildView() {
  936. return child;
  937. }
  938. /**
  939. * Update the child state. This should be
  940. * called by the thread that desires to spend
  941. * time updating the child state (intended to
  942. * be the layout thread).
  943. * <p>
  944. * This aquires a read lock on the associated
  945. * document for the duration of the update to
  946. * ensure the model is not changed while it is
  947. * operating. The first thing to do would be
  948. * to see if any work actually needs to be done.
  949. * The following could have conceivably happened
  950. * while the state was waiting to be updated:
  951. * <ol>
  952. * <li>The child may have been removed from the
  953. * view hierarchy.
  954. * <li>The child may have been updated by a
  955. * higher priority operation (i.e. the child
  956. * may have become visible).
  957. * </ol>
  958. */
  959. public void run () {
  960. AbstractDocument doc = (AbstractDocument) getDocument();
  961. try {
  962. doc.readLock();
  963. if (minorValid && majorValid && childSizeValid) {
  964. // nothing to do
  965. return;
  966. }
  967. if (child.getParent() == AsyncBoxView.this) {
  968. // this may overwrite anothers threads cached
  969. // value for actively changing... but that just
  970. // means it won't use the cache if there is an
  971. // overwrite.
  972. synchronized(AsyncBoxView.this) {
  973. changing = this;
  974. }
  975. updateChild();
  976. synchronized(AsyncBoxView.this) {
  977. changing = null;
  978. }
  979. // setting the child size on the minor axis
  980. // may have caused it to change it's preference
  981. // along the major axis.
  982. updateChild();
  983. }
  984. } finally {
  985. doc.readUnlock();
  986. }
  987. }
  988. void updateChild() {
  989. boolean minorUpdated = false;
  990. synchronized(this) {
  991. if (! minorValid) {
  992. int minorAxis = getMinorAxis();
  993. min = child.getMinimumSpan(minorAxis);
  994. pref = child.getPreferredSpan(minorAxis);
  995. max = child.getMaximumSpan(minorAxis);
  996. minorValid = true;
  997. minorUpdated = true;
  998. }
  999. }
  1000. if (minorUpdated) {
  1001. minorRequirementChange(this);
  1002. }
  1003. boolean majorUpdated = false;
  1004. float delta = 0.0f;
  1005. synchronized(this) {
  1006. if (! majorValid) {
  1007. float old = span;
  1008. span = child.getPreferredSpan(axis);
  1009. delta = span - old;
  1010. majorValid = true;
  1011. majorUpdated = true;
  1012. }
  1013. }
  1014. if (majorUpdated) {
  1015. majorRequirementChange(this, delta);
  1016. locator.childChanged(this);
  1017. }
  1018. synchronized(this) {
  1019. if (! childSizeValid) {
  1020. float w;
  1021. float h;
  1022. if (axis == X_AXIS) {
  1023. w = span;
  1024. h = getMinorSpan();
  1025. } else {
  1026. w = getMinorSpan();
  1027. h = span;
  1028. }
  1029. childSizeValid = true;
  1030. child.setSize(w, h);
  1031. }
  1032. }
  1033. }
  1034. /**
  1035. * What is the span along the minor axis.
  1036. */
  1037. public float getMinorSpan() {
  1038. if (max < minorSpan) {
  1039. return max;
  1040. }
  1041. // make it the target width, or as small as it can get.
  1042. return Math.max(min, minorSpan);
  1043. }
  1044. /**
  1045. * What is the offset along the minor axis
  1046. */
  1047. public float getMinorOffset() {
  1048. if (max < minorSpan) {
  1049. // can't make the child this wide, align it
  1050. float align = child.getAlignment(getMinorAxis());
  1051. return ((minorSpan - max) * align);
  1052. }
  1053. return 0f;
  1054. }
  1055. /**
  1056. * What is the span along the major axis.
  1057. */
  1058. public float getMajorSpan() {
  1059. return span;
  1060. }
  1061. /**
  1062. * Get the offset along the major axis
  1063. */
  1064. public float getMajorOffset() {
  1065. return offset;
  1066. }
  1067. /**
  1068. * This method should only be called by the ChildLocator,
  1069. * it is simply a convenient place to hold the cached
  1070. * location.
  1071. */
  1072. public void setMajorOffset(float offs) {
  1073. offset = offs;
  1074. }
  1075. /**
  1076. * Mark preferences changed for this child.
  1077. *
  1078. * @param width true if the width preference has changed
  1079. * @param height true if the height preference has changed
  1080. * @see javax.swing.JComponent#revalidate
  1081. */
  1082. public void preferenceChanged(boolean width, boolean height) {
  1083. if (axis == X_AXIS) {
  1084. if (width) {
  1085. majorValid = false;
  1086. }
  1087. if (height) {
  1088. minorValid = false;
  1089. }
  1090. } else {
  1091. if (width) {
  1092. minorValid = false;
  1093. }
  1094. if (height) {
  1095. majorValid = false;
  1096. }
  1097. }
  1098. childSizeValid = false;
  1099. }
  1100. /**
  1101. * Has the child view been laid out.
  1102. */
  1103. public boolean isLayoutValid() {
  1104. return (minorValid && majorValid && childSizeValid);
  1105. }
  1106. // minor axis
  1107. private float min;
  1108. private float pref;
  1109. private float max;
  1110. private float align;
  1111. private boolean minorValid;
  1112. // major axis
  1113. private float span;
  1114. private float offset;
  1115. private boolean majorValid;
  1116. private View child;
  1117. private boolean childSizeValid;
  1118. }
  1119. /**
  1120. * Task to flush requirement changes upward
  1121. */
  1122. class FlushTask implements Runnable {
  1123. public void run() {
  1124. AbstractDocument doc = (AbstractDocument) getDocument();
  1125. try {
  1126. doc.readLock();
  1127. int n = getViewCount();
  1128. if (minorChanged && (n > 0)) {
  1129. LayoutQueue q = getLayoutQueue();
  1130. ChildState min = getChildState(0);
  1131. ChildState pref = getChildState(0);
  1132. for (int i = 1; i < n; i++) {
  1133. ChildState cs = getChildState(i);
  1134. if (cs.min > min.min) {
  1135. min = cs;
  1136. }
  1137. if (cs.pref > pref.pref) {
  1138. pref = cs;
  1139. }
  1140. }
  1141. synchronized (AsyncBoxView.this) {
  1142. minRequest = min;
  1143. prefRequest = pref;
  1144. }
  1145. }
  1146. flushRequirementChanges();
  1147. } finally {
  1148. doc.readUnlock();
  1149. }
  1150. }
  1151. }
  1152. /**
  1153. * Collection to hold status records.
  1154. */
  1155. static class ChildVector extends GapVector {
  1156. ChildVector() {
  1157. super();
  1158. }
  1159. ChildVector(int size) {
  1160. super(size);
  1161. }
  1162. public void replace(int i, int rmSize, ChildState[] items) {
  1163. super.replace(i, rmSize, items, items.length);
  1164. }
  1165. /**
  1166. * Allocate an array to store items of the type
  1167. * appropriate (which is determined by the subclass).
  1168. */
  1169. protected Object allocateArray(int len) {
  1170. return new ChildState[len];
  1171. }
  1172. /**
  1173. * Get the length of the allocated array
  1174. */
  1175. protected int getArrayLength() {
  1176. ChildState[] a = (ChildState[]) getArray();
  1177. return a.length;
  1178. }
  1179. /**
  1180. * Returns the number of marks currently held
  1181. */
  1182. public int size() {
  1183. int len = getArrayLength() - (getGapEnd() - getGapStart());
  1184. return len;
  1185. }
  1186. /**
  1187. * Fetches the child state at the given index
  1188. */
  1189. public ChildState getChildState(int index) {
  1190. int g0 = getGapStart();
  1191. int g1 = getGapEnd();
  1192. ChildState[] array = (ChildState[]) getArray();
  1193. if (index < g0) {
  1194. // below gap
  1195. return array[index];
  1196. } else {
  1197. // above gap
  1198. index += g1 - g0;
  1199. return array[index];
  1200. }
  1201. }
  1202. }
  1203. }