1. /*
  2. * @(#)BoxView.java 1.61 04/03/05
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.text;
  8. import java.io.PrintStream;
  9. import java.util.Vector;
  10. import java.awt.*;
  11. import javax.swing.event.DocumentEvent;
  12. import javax.swing.SizeRequirements;
  13. /**
  14. * A view that arranges its children into a box shape by tiling
  15. * its children along an axis. The box is somewhat like that
  16. * found in TeX where there is alignment of the
  17. * children, flexibility of the children is considered, etc.
  18. * This is a building block that might be useful to represent
  19. * things like a collection of lines, paragraphs,
  20. * lists, columns, pages, etc. The axis along which the children are tiled is
  21. * considered the major axis. The orthoginal axis is the minor axis.
  22. * <p>
  23. * Layout for each axis is handled separately by the methods
  24. * <code>layoutMajorAxis</code> and <code>layoutMinorAxis</code>.
  25. * Subclasses can change the layout algorithm by
  26. * reimplementing these methods. These methods will be called
  27. * as necessary depending upon whether or not there is cached
  28. * layout information and the cache is considered
  29. * valid. These methods are typically called if the given size
  30. * along the axis changes, or if <code>layoutChanged</code> is
  31. * called to force an updated layout. The <code>layoutChanged</code>
  32. * method invalidates cached layout information, if there is any.
  33. * The requirements published to the parent view are calculated by
  34. * the methods <code>calculateMajorAxisRequirements</code>
  35. * and <code>calculateMinorAxisRequirements</code>.
  36. * If the layout algorithm is changed, these methods will
  37. * likely need to be reimplemented.
  38. *
  39. * @author Timothy Prinzing
  40. * @version 1.61 03/05/04
  41. */
  42. public class BoxView extends CompositeView {
  43. /**
  44. * Constructs a <code>BoxView</code>.
  45. *
  46. * @param elem the element this view is responsible for
  47. * @param axis either <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>
  48. */
  49. public BoxView(Element elem, int axis) {
  50. super(elem);
  51. tempRect = new Rectangle();
  52. this.majorAxis = axis;
  53. majorOffsets = new int[0];
  54. majorSpans = new int[0];
  55. majorReqValid = false;
  56. majorAllocValid = false;
  57. minorOffsets = new int[0];
  58. minorSpans = new int[0];
  59. minorReqValid = false;
  60. minorAllocValid = false;
  61. }
  62. /**
  63. * Fetches the tile axis property. This is the axis along which
  64. * the child views are tiled.
  65. *
  66. * @return the major axis of the box, either
  67. * <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>
  68. *
  69. * @since 1.3
  70. */
  71. public int getAxis() {
  72. return majorAxis;
  73. }
  74. /**
  75. * Sets the tile axis property. This is the axis along which
  76. * the child views are tiled.
  77. *
  78. * @param axis either <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>
  79. *
  80. * @since 1.3
  81. */
  82. public void setAxis(int axis) {
  83. boolean axisChanged = (axis != majorAxis);
  84. majorAxis = axis;
  85. if (axisChanged) {
  86. preferenceChanged(null, true, true);
  87. }
  88. }
  89. /**
  90. * Invalidates the layout along an axis. This happens
  91. * automatically if the preferences have changed for
  92. * any of the child views. In some cases the layout
  93. * may need to be recalculated when the preferences
  94. * have not changed. The layout can be marked as
  95. * invalid by calling this method. The layout will
  96. * be updated the next time the <code>setSize</code> method
  97. * is called on this view (typically in paint).
  98. *
  99. * @param axis either <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>
  100. *
  101. * @since 1.3
  102. */
  103. public void layoutChanged(int axis) {
  104. if (axis == majorAxis) {
  105. majorAllocValid = false;
  106. } else {
  107. minorAllocValid = false;
  108. }
  109. }
  110. /**
  111. * Determines if the layout is valid along the given axis.
  112. *
  113. * @param axis either <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>
  114. *
  115. * @since 1.4
  116. */
  117. protected boolean isLayoutValid(int axis) {
  118. if (axis == majorAxis) {
  119. return majorAllocValid;
  120. } else {
  121. return minorAllocValid;
  122. }
  123. }
  124. /**
  125. * Paints a child. By default
  126. * that is all it does, but a subclass can use this to paint
  127. * things relative to the child.
  128. *
  129. * @param g the graphics context
  130. * @param alloc the allocated region to paint into
  131. * @param index the child index, >= 0 && < getViewCount()
  132. */
  133. protected void paintChild(Graphics g, Rectangle alloc, int index) {
  134. View child = getView(index);
  135. child.paint(g, alloc);
  136. }
  137. // --- View methods ---------------------------------------------
  138. /**
  139. * Invalidates the layout and resizes the cache of
  140. * requests/allocations. The child allocations can still
  141. * be accessed for the old layout, but the new children
  142. * will have an offset and span of 0.
  143. *
  144. * @param index the starting index into the child views to insert
  145. * the new views; this should be a value >= 0 and <= getViewCount
  146. * @param length the number of existing child views to remove;
  147. * This should be a value >= 0 and <= (getViewCount() - offset)
  148. * @param elems the child views to add; this value can be
  149. * <code>null</code>to indicate no children are being added
  150. * (useful to remove)
  151. */
  152. public void replace(int index, int length, View[] elems) {
  153. super.replace(index, length, elems);
  154. // invalidate cache
  155. int nInserted = (elems != null) ? elems.length : 0;
  156. majorOffsets = updateLayoutArray(majorOffsets, index, nInserted);
  157. majorSpans = updateLayoutArray(majorSpans, index, nInserted);
  158. majorReqValid = false;
  159. majorAllocValid = false;
  160. minorOffsets = updateLayoutArray(minorOffsets, index, nInserted);
  161. minorSpans = updateLayoutArray(minorSpans, index, nInserted);
  162. minorReqValid = false;
  163. minorAllocValid = false;
  164. }
  165. /**
  166. * Resizes the given layout array to match the new number of
  167. * child views. The current number of child views are used to
  168. * produce the new array. The contents of the old array are
  169. * inserted into the new array at the appropriate places so that
  170. * the old layout information is transferred to the new array.
  171. *
  172. * @param oldArray the original layout array
  173. * @param offset location where new views will be inserted
  174. * @param nInserted the number of child views being inserted;
  175. * therefore the number of blank spaces to leave in the
  176. * new array at location <code>offset</code>
  177. * @return the new layout array
  178. */
  179. int[] updateLayoutArray(int[] oldArray, int offset, int nInserted) {
  180. int n = getViewCount();
  181. int[] newArray = new int[n];
  182. System.arraycopy(oldArray, 0, newArray, 0, offset);
  183. System.arraycopy(oldArray, offset,
  184. newArray, offset + nInserted, n - nInserted - offset);
  185. return newArray;
  186. }
  187. /**
  188. * Forwards the given <code>DocumentEvent</code> to the child views
  189. * that need to be notified of the change to the model.
  190. * If a child changed its requirements and the allocation
  191. * was valid prior to forwarding the portion of the box
  192. * from the starting child to the end of the box will
  193. * be repainted.
  194. *
  195. * @param ec changes to the element this view is responsible
  196. * for (may be <code>null</code> if there were no changes)
  197. * @param e the change information from the associated document
  198. * @param a the current allocation of the view
  199. * @param f the factory to use to rebuild if the view has children
  200. * @see #insertUpdate
  201. * @see #removeUpdate
  202. * @see #changedUpdate
  203. */
  204. protected void forwardUpdate(DocumentEvent.ElementChange ec,
  205. DocumentEvent e, Shape a, ViewFactory f) {
  206. boolean wasValid = isLayoutValid(majorAxis);
  207. super.forwardUpdate(ec, e, a, f);
  208. // determine if a repaint is needed
  209. if (wasValid && (! isLayoutValid(majorAxis))) {
  210. // Repaint is needed because one of the tiled children
  211. // have changed their span along the major axis. If there
  212. // is a hosting component and an allocated shape we repaint.
  213. Component c = getContainer();
  214. if ((a != null) && (c != null)) {
  215. int pos = e.getOffset();
  216. int index = getViewIndexAtPosition(pos);
  217. Rectangle alloc = getInsideAllocation(a);
  218. if (majorAxis == X_AXIS) {
  219. alloc.x += majorOffsets[index];
  220. alloc.width -= majorOffsets[index];
  221. } else {
  222. alloc.y += minorOffsets[index];
  223. alloc.height -= minorOffsets[index];
  224. }
  225. c.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
  226. }
  227. }
  228. }
  229. /**
  230. * This is called by a child to indicate its
  231. * preferred span has changed. This is implemented to
  232. * throw away cached layout information so that new
  233. * calculations will be done the next time the children
  234. * need an allocation.
  235. *
  236. * @param child the child view
  237. * @param width true if the width preference should change
  238. * @param height true if the height preference should change
  239. */
  240. public void preferenceChanged(View child, boolean width, boolean height) {
  241. boolean majorChanged = (majorAxis == X_AXIS) ? width : height;
  242. boolean minorChanged = (majorAxis == X_AXIS) ? height : width;
  243. if (majorChanged) {
  244. majorReqValid = false;
  245. majorAllocValid = false;
  246. }
  247. if (minorChanged) {
  248. minorReqValid = false;
  249. minorAllocValid = false;
  250. }
  251. super.preferenceChanged(child, width, height);
  252. }
  253. /**
  254. * Gets the resize weight. A value of 0 or less is not resizable.
  255. *
  256. * @param axis may be either <code>View.X_AXIS</code> or
  257. * <code>View.Y_AXIS</code>
  258. * @return the weight
  259. * @exception IllegalArgumentException for an invalid axis
  260. */
  261. public int getResizeWeight(int axis) {
  262. checkRequests(axis);
  263. if (axis == majorAxis) {
  264. if ((majorRequest.preferred != majorRequest.minimum) ||
  265. (majorRequest.preferred != majorRequest.maximum)) {
  266. return 1;
  267. }
  268. } else {
  269. if ((minorRequest.preferred != minorRequest.minimum) ||
  270. (minorRequest.preferred != minorRequest.maximum)) {
  271. return 1;
  272. }
  273. }
  274. return 0;
  275. }
  276. /**
  277. * Sets the size of the view along an axis. This should cause
  278. * layout of the view along the given axis.
  279. *
  280. * @param axis may be either <code>View.X_AXIS</code> or
  281. * <code>View.Y_AXIS</code>
  282. * @param span the span to layout to >= 0
  283. */
  284. void setSpanOnAxis(int axis, float span) {
  285. if (axis == majorAxis) {
  286. if (majorSpan != (int) span) {
  287. majorAllocValid = false;
  288. }
  289. if (! majorAllocValid) {
  290. // layout the major axis
  291. majorSpan = (int) span;
  292. checkRequests(majorAxis);
  293. layoutMajorAxis(majorSpan, axis, majorOffsets, majorSpans);
  294. majorAllocValid = true;
  295. // flush changes to the children
  296. updateChildSizes();
  297. }
  298. } else {
  299. if (((int) span) != minorSpan) {
  300. minorAllocValid = false;
  301. }
  302. if (! minorAllocValid) {
  303. // layout the minor axis
  304. minorSpan = (int) span;
  305. checkRequests(axis);
  306. layoutMinorAxis(minorSpan, axis, minorOffsets, minorSpans);
  307. minorAllocValid = true;
  308. // flush changes to the children
  309. updateChildSizes();
  310. }
  311. }
  312. }
  313. /**
  314. * Propagates the current allocations to the child views.
  315. */
  316. void updateChildSizes() {
  317. int n = getViewCount();
  318. if (majorAxis == X_AXIS) {
  319. for (int i = 0; i < n; i++) {
  320. View v = getView(i);
  321. v.setSize((float) majorSpans[i], (float) minorSpans[i]);
  322. }
  323. } else {
  324. for (int i = 0; i < n; i++) {
  325. View v = getView(i);
  326. v.setSize((float) minorSpans[i], (float) majorSpans[i]);
  327. }
  328. }
  329. }
  330. /**
  331. * Returns the size of the view along an axis. This is implemented
  332. * to return zero.
  333. *
  334. * @param axis may be either <code>View.X_AXIS</code> or
  335. * <code>View.Y_AXIS</code>
  336. * @return the current span of the view along the given axis, >= 0
  337. */
  338. float getSpanOnAxis(int axis) {
  339. if (axis == majorAxis) {
  340. return majorSpan;
  341. } else {
  342. return minorSpan;
  343. }
  344. }
  345. /**
  346. * Sets the size of the view. This should cause
  347. * layout of the view if the view caches any layout
  348. * information. This is implemented to call the
  349. * layout method with the sizes inside of the insets.
  350. *
  351. * @param width the width >= 0
  352. * @param height the height >= 0
  353. */
  354. public void setSize(float width, float height) {
  355. layout((int)(width - getLeftInset() - getRightInset()),
  356. (int)(height - getTopInset() - getBottomInset()));
  357. }
  358. /**
  359. * Renders the <code>BoxView</code> using the given
  360. * rendering surface and area
  361. * on that surface. Only the children that intersect
  362. * the clip bounds of the given <code>Graphics</code>
  363. * will be rendered.
  364. *
  365. * @param g the rendering surface to use
  366. * @param allocation the allocated region to render into
  367. * @see View#paint
  368. */
  369. public void paint(Graphics g, Shape allocation) {
  370. Rectangle alloc = (allocation instanceof Rectangle) ?
  371. (Rectangle)allocation : allocation.getBounds();
  372. int n = getViewCount();
  373. int x = alloc.x + getLeftInset();
  374. int y = alloc.y + getTopInset();
  375. Rectangle clip = g.getClipBounds();
  376. for (int i = 0; i < n; i++) {
  377. tempRect.x = x + getOffset(X_AXIS, i);
  378. tempRect.y = y + getOffset(Y_AXIS, i);
  379. tempRect.width = getSpan(X_AXIS, i);
  380. tempRect.height = getSpan(Y_AXIS, i);
  381. if (tempRect.intersects(clip)) {
  382. paintChild(g, tempRect, i);
  383. }
  384. }
  385. }
  386. /**
  387. * Fetches the allocation for the given child view.
  388. * This enables finding out where various views
  389. * are located. This is implemented to return
  390. * <code>null</code> if the layout is invalid,
  391. * otherwise the superclass behavior is executed.
  392. *
  393. * @param index the index of the child, >= 0 && < getViewCount()
  394. * @param a the allocation to this view
  395. * @return the allocation to the child; or <code>null</code>
  396. * if <code>a</code> is <code>null</code>
  397. * or <code>null</code> if the layout is invalid
  398. */
  399. public Shape getChildAllocation(int index, Shape a) {
  400. if (a != null) {
  401. Shape ca = super.getChildAllocation(index, a);
  402. if ((ca != null) && (! isAllocationValid())) {
  403. // The child allocation may not have been set yet.
  404. Rectangle r = (ca instanceof Rectangle) ?
  405. (Rectangle) ca : ca.getBounds();
  406. if ((r.width == 0) && (r.height == 0)) {
  407. return null;
  408. }
  409. }
  410. return ca;
  411. }
  412. return null;
  413. }
  414. /**
  415. * Provides a mapping from the document model coordinate space
  416. * to the coordinate space of the view mapped to it. This makes
  417. * sure the allocation is valid before calling the superclass.
  418. *
  419. * @param pos the position to convert >= 0
  420. * @param a the allocated region to render into
  421. * @return the bounding box of the given position
  422. * @exception BadLocationException if the given position does
  423. * not represent a valid location in the associated document
  424. * @see View#modelToView
  425. */
  426. public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
  427. if (! isAllocationValid()) {
  428. Rectangle alloc = a.getBounds();
  429. setSize(alloc.width, alloc.height);
  430. }
  431. return super.modelToView(pos, a, b);
  432. }
  433. /**
  434. * Provides a mapping from the view coordinate space to the logical
  435. * coordinate space of the model.
  436. *
  437. * @param x x coordinate of the view location to convert >= 0
  438. * @param y y coordinate of the view location to convert >= 0
  439. * @param a the allocated region to render into
  440. * @return the location within the model that best represents the
  441. * given point in the view >= 0
  442. * @see View#viewToModel
  443. */
  444. public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
  445. if (! isAllocationValid()) {
  446. Rectangle alloc = a.getBounds();
  447. setSize(alloc.width, alloc.height);
  448. }
  449. return super.viewToModel(x, y, a, bias);
  450. }
  451. /**
  452. * Determines the desired alignment for this view along an
  453. * axis. This is implemented to give the total alignment
  454. * needed to position the children with the alignment points
  455. * lined up along the axis orthoginal to the axis that is
  456. * being tiled. The axis being tiled will request to be
  457. * centered (i.e. 0.5f).
  458. *
  459. * @param axis may be either <code>View.X_AXIS</code>
  460. * or <code>View.Y_AXIS</code>
  461. * @return the desired alignment >= 0.0f && <= 1.0f; this should
  462. * be a value between 0.0 and 1.0 where 0 indicates alignment at the
  463. * origin and 1.0 indicates alignment to the full span
  464. * away from the origin; an alignment of 0.5 would be the
  465. * center of the view
  466. * @exception IllegalArgumentException for an invalid axis
  467. */
  468. public float getAlignment(int axis) {
  469. checkRequests(axis);
  470. if (axis == majorAxis) {
  471. return majorRequest.alignment;
  472. } else {
  473. return minorRequest.alignment;
  474. }
  475. }
  476. /**
  477. * Determines the preferred span for this view along an
  478. * axis.
  479. *
  480. * @param axis may be either <code>View.X_AXIS</code>
  481. * or <code>View.Y_AXIS</code>
  482. * @return the span the view would like to be rendered into >= 0;
  483. * typically the view is told to render into the span
  484. * that is returned, although there is no guarantee;
  485. * the parent may choose to resize or break the view
  486. * @exception IllegalArgumentException for an invalid axis type
  487. */
  488. public float getPreferredSpan(int axis) {
  489. checkRequests(axis);
  490. float marginSpan = (axis == X_AXIS) ? getLeftInset() + getRightInset() :
  491. getTopInset() + getBottomInset();
  492. if (axis == majorAxis) {
  493. return ((float)majorRequest.preferred) + marginSpan;
  494. } else {
  495. return ((float)minorRequest.preferred) + marginSpan;
  496. }
  497. }
  498. /**
  499. * Determines the minimum span for this view along an
  500. * axis.
  501. *
  502. * @param axis may be either <code>View.X_AXIS</code>
  503. * or <code>View.Y_AXIS</code>
  504. * @return the span the view would like to be rendered into >= 0;
  505. * typically the view is told to render into the span
  506. * that is returned, although there is no guarantee;
  507. * the parent may choose to resize or break the view
  508. * @exception IllegalArgumentException for an invalid axis type
  509. */
  510. public float getMinimumSpan(int axis) {
  511. checkRequests(axis);
  512. float marginSpan = (axis == X_AXIS) ? getLeftInset() + getRightInset() :
  513. getTopInset() + getBottomInset();
  514. if (axis == majorAxis) {
  515. return ((float)majorRequest.minimum) + marginSpan;
  516. } else {
  517. return ((float)minorRequest.minimum) + marginSpan;
  518. }
  519. }
  520. /**
  521. * Determines the maximum span for this view along an
  522. * axis.
  523. *
  524. * @param axis may be either <code>View.X_AXIS</code>
  525. * or <code>View.Y_AXIS</code>
  526. * @return the span the view would like to be rendered into >= 0;
  527. * typically the view is told to render into the span
  528. * that is returned, although there is no guarantee;
  529. * the parent may choose to resize or break the view
  530. * @exception IllegalArgumentException for an invalid axis type
  531. */
  532. public float getMaximumSpan(int axis) {
  533. checkRequests(axis);
  534. float marginSpan = (axis == X_AXIS) ? getLeftInset() + getRightInset() :
  535. getTopInset() + getBottomInset();
  536. if (axis == majorAxis) {
  537. return ((float)majorRequest.maximum) + marginSpan;
  538. } else {
  539. return ((float)minorRequest.maximum) + marginSpan;
  540. }
  541. }
  542. // --- local methods ----------------------------------------------------
  543. /**
  544. * Are the allocations for the children still
  545. * valid?
  546. *
  547. * @return true if allocations still valid
  548. */
  549. protected boolean isAllocationValid() {
  550. return (majorAllocValid && minorAllocValid);
  551. }
  552. /**
  553. * Determines if a point falls before an allocated region.
  554. *
  555. * @param x the X coordinate >= 0
  556. * @param y the Y coordinate >= 0
  557. * @param innerAlloc the allocated region; this is the area
  558. * inside of the insets
  559. * @return true if the point lies before the region else false
  560. */
  561. protected boolean isBefore(int x, int y, Rectangle innerAlloc) {
  562. if (majorAxis == View.X_AXIS) {
  563. return (x < innerAlloc.x);
  564. } else {
  565. return (y < innerAlloc.y);
  566. }
  567. }
  568. /**
  569. * Determines if a point falls after an allocated region.
  570. *
  571. * @param x the X coordinate >= 0
  572. * @param y the Y coordinate >= 0
  573. * @param innerAlloc the allocated region; this is the area
  574. * inside of the insets
  575. * @return true if the point lies after the region else false
  576. */
  577. protected boolean isAfter(int x, int y, Rectangle innerAlloc) {
  578. if (majorAxis == View.X_AXIS) {
  579. return (x > (innerAlloc.width + innerAlloc.x));
  580. } else {
  581. return (y > (innerAlloc.height + innerAlloc.y));
  582. }
  583. }
  584. /**
  585. * Fetches the child view at the given coordinates.
  586. *
  587. * @param x the X coordinate >= 0
  588. * @param y the Y coordinate >= 0
  589. * @param alloc the parents inner allocation on entry, which should
  590. * be changed to the childs allocation on exit
  591. * @return the view
  592. */
  593. protected View getViewAtPoint(int x, int y, Rectangle alloc) {
  594. int n = getViewCount();
  595. if (majorAxis == View.X_AXIS) {
  596. if (x < (alloc.x + majorOffsets[0])) {
  597. childAllocation(0, alloc);
  598. return getView(0);
  599. }
  600. for (int i = 0; i < n; i++) {
  601. if (x < (alloc.x + majorOffsets[i])) {
  602. childAllocation(i - 1, alloc);
  603. return getView(i - 1);
  604. }
  605. }
  606. childAllocation(n - 1, alloc);
  607. return getView(n - 1);
  608. } else {
  609. if (y < (alloc.y + majorOffsets[0])) {
  610. childAllocation(0, alloc);
  611. return getView(0);
  612. }
  613. for (int i = 0; i < n; i++) {
  614. if (y < (alloc.y + majorOffsets[i])) {
  615. childAllocation(i - 1, alloc);
  616. return getView(i - 1);
  617. }
  618. }
  619. childAllocation(n - 1, alloc);
  620. return getView(n - 1);
  621. }
  622. }
  623. /**
  624. * Allocates a region for a child view.
  625. *
  626. * @param index the index of the child view to
  627. * allocate, >= 0 && < getViewCount()
  628. * @param alloc the allocated region
  629. */
  630. protected void childAllocation(int index, Rectangle alloc) {
  631. alloc.x += getOffset(X_AXIS, index);
  632. alloc.y += getOffset(Y_AXIS, index);
  633. alloc.width = getSpan(X_AXIS, index);
  634. alloc.height = getSpan(Y_AXIS, index);
  635. }
  636. /**
  637. * Perform layout on the box
  638. *
  639. * @param width the width (inside of the insets) >= 0
  640. * @param height the height (inside of the insets) >= 0
  641. */
  642. protected void layout(int width, int height) {
  643. setSpanOnAxis(X_AXIS, width);
  644. setSpanOnAxis(Y_AXIS, height);
  645. }
  646. /**
  647. * Returns the current width of the box. This is the width that
  648. * it was last allocated.
  649. * @return the current width of the box
  650. */
  651. public int getWidth() {
  652. int span;
  653. if (majorAxis == X_AXIS) {
  654. span = majorSpan;
  655. } else {
  656. span = minorSpan;
  657. }
  658. span += getLeftInset() - getRightInset();
  659. return span;
  660. }
  661. /**
  662. * Returns the current height of the box. This is the height that
  663. * it was last allocated.
  664. * @return the current height of the box
  665. */
  666. public int getHeight() {
  667. int span;
  668. if (majorAxis == Y_AXIS) {
  669. span = majorSpan;
  670. } else {
  671. span = minorSpan;
  672. }
  673. span += getTopInset() - getBottomInset();
  674. return span;
  675. }
  676. /**
  677. * Performs layout for the major axis of the box (i.e. the
  678. * axis that it represents). The results of the layout should
  679. * be placed in the given arrays which represent the allocations
  680. * to the children along the major axis.
  681. *
  682. * @param targetSpan the total span given to the view, which
  683. * would be used to layout the children
  684. * @param axis the axis being layed out
  685. * @param offsets the offsets from the origin of the view for
  686. * each of the child views; this is a return value and is
  687. * filled in by the implementation of this method
  688. * @param spans the span of each child view; this is a return
  689. * value and is filled in by the implementation of this method
  690. * @return the offset and span for each child view in the
  691. * offsets and spans parameters
  692. */
  693. protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
  694. /*
  695. * first pass, calculate the preferred sizes
  696. * and the flexibility to adjust the sizes.
  697. */
  698. long preferred = 0;
  699. int n = getViewCount();
  700. for (int i = 0; i < n; i++) {
  701. View v = getView(i);
  702. spans[i] = (int) v.getPreferredSpan(axis);
  703. preferred += spans[i];
  704. }
  705. /*
  706. * Second pass, expand or contract by as much as possible to reach
  707. * the target span.
  708. */
  709. // determine the adjustment to be made
  710. long desiredAdjustment = targetSpan - preferred;
  711. float adjustmentFactor = 0.0f;
  712. int[] diffs = null;
  713. if (desiredAdjustment != 0) {
  714. long totalSpan = 0;
  715. diffs = new int[n];
  716. for (int i = 0; i < n; i++) {
  717. View v = getView(i);
  718. int tmp;
  719. if (desiredAdjustment < 0) {
  720. tmp = (int)v.getMinimumSpan(axis);
  721. diffs[i] = spans[i] - tmp;
  722. } else {
  723. tmp = (int)v.getMaximumSpan(axis);
  724. diffs[i] = tmp - spans[i];
  725. }
  726. totalSpan += tmp;
  727. }
  728. float maximumAdjustment = Math.abs(totalSpan - preferred);
  729. adjustmentFactor = desiredAdjustment / maximumAdjustment;
  730. adjustmentFactor = Math.min(adjustmentFactor, 1.0f);
  731. adjustmentFactor = Math.max(adjustmentFactor, -1.0f);
  732. }
  733. // make the adjustments
  734. int totalOffset = 0;
  735. for (int i = 0; i < n; i++) {
  736. offsets[i] = totalOffset;
  737. if (desiredAdjustment != 0) {
  738. float adjF = adjustmentFactor * diffs[i];
  739. spans[i] += Math.round(adjF);
  740. }
  741. totalOffset = (int) Math.min((long) totalOffset + (long) spans[i], Integer.MAX_VALUE);
  742. }
  743. }
  744. /**
  745. * Performs layout for the minor axis of the box (i.e. the
  746. * axis orthoginal to the axis that it represents). The results
  747. * of the layout should be placed in the given arrays which represent
  748. * the allocations to the children along the minor axis.
  749. *
  750. * @param targetSpan the total span given to the view, which
  751. * would be used to layout the children
  752. * @param axis the axis being layed out
  753. * @param offsets the offsets from the origin of the view for
  754. * each of the child views; this is a return value and is
  755. * filled in by the implementation of this method
  756. * @param spans the span of each child view; this is a return
  757. * value and is filled in by the implementation of this method
  758. * @return the offset and span for each child view in the
  759. * offsets and spans parameters
  760. */
  761. protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
  762. int n = getViewCount();
  763. for (int i = 0; i < n; i++) {
  764. View v = getView(i);
  765. int max = (int) v.getMaximumSpan(axis);
  766. if (max < targetSpan) {
  767. // can't make the child this wide, align it
  768. float align = v.getAlignment(axis);
  769. offsets[i] = (int) ((targetSpan - max) * align);
  770. spans[i] = max;
  771. } else {
  772. // make it the target width, or as small as it can get.
  773. int min = (int)v.getMinimumSpan(axis);
  774. offsets[i] = 0;
  775. spans[i] = Math.max(min, targetSpan);
  776. }
  777. }
  778. }
  779. /**
  780. * Calculates the size requirements for the major axis
  781. * </code>axis</code>.
  782. *
  783. * @param axis the axis being studied
  784. * @param r the <code>SizeRequirements</code> object;
  785. * if <code>null</code> one will be created
  786. * @return the newly initialized <code>SizeRequirements</code> object
  787. * @see javax.swing.SizeRequirements
  788. */
  789. protected SizeRequirements calculateMajorAxisRequirements(int axis, SizeRequirements r) {
  790. // calculate tiled request
  791. float min = 0;
  792. float pref = 0;
  793. float max = 0;
  794. int n = getViewCount();
  795. for (int i = 0; i < n; i++) {
  796. View v = getView(i);
  797. min += v.getMinimumSpan(axis);
  798. pref += v.getPreferredSpan(axis);
  799. max += v.getMaximumSpan(axis);
  800. }
  801. if (r == null) {
  802. r = new SizeRequirements();
  803. }
  804. r.alignment = 0.5f;
  805. r.minimum = (int) min;
  806. r.preferred = (int) pref;
  807. r.maximum = (int) max;
  808. return r;
  809. }
  810. /**
  811. * Calculates the size requirements for the minor axis
  812. * <code>axis</code>.
  813. *
  814. * @param axis the axis being studied
  815. * @param r the <code>SizeRequirements</code> object;
  816. * if <code>null</code> one will be created
  817. * @return the newly initialized <code>SizeRequirements</code> object
  818. * @see javax.swing.SizeRequirements
  819. */
  820. protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) {
  821. int min = 0;
  822. long pref = 0;
  823. int max = Integer.MAX_VALUE;
  824. int n = getViewCount();
  825. for (int i = 0; i < n; i++) {
  826. View v = getView(i);
  827. min = Math.max((int) v.getMinimumSpan(axis), min);
  828. pref = Math.max((int) v.getPreferredSpan(axis), pref);
  829. max = Math.max((int) v.getMaximumSpan(axis), max);
  830. }
  831. if (r == null) {
  832. r = new SizeRequirements();
  833. r.alignment = 0.5f;
  834. }
  835. r.preferred = (int) pref;
  836. r.minimum = min;
  837. r.maximum = max;
  838. return r;
  839. }
  840. /**
  841. * Checks the request cache and update if needed.
  842. * @param axis the axis being studied
  843. * @exception IllegalArgumentException if <code>axis</code> is
  844. * neither <code>View.X_AXIS</code> nor </code>View.Y_AXIS</code>
  845. */
  846. void checkRequests(int axis) {
  847. if ((axis != X_AXIS) && (axis != Y_AXIS)) {
  848. throw new IllegalArgumentException("Invalid axis: " + axis);
  849. }
  850. if (axis == majorAxis) {
  851. if (!majorReqValid) {
  852. majorRequest = calculateMajorAxisRequirements(axis,
  853. majorRequest);
  854. majorReqValid = true;
  855. }
  856. } else if (! minorReqValid) {
  857. minorRequest = calculateMinorAxisRequirements(axis, minorRequest);
  858. minorReqValid = true;
  859. }
  860. }
  861. /**
  862. * Computes the location and extent of each child view
  863. * in this <code>BoxView</code> given the <code>targetSpan</code>,
  864. * which is the width (or height) of the region we have to
  865. * work with.
  866. *
  867. * @param targetSpan the total span given to the view, which
  868. * would be used to layout the children
  869. * @param axis the axis being studied, either
  870. * <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>
  871. * @param offsets an empty array filled by this method with
  872. * values specifying the location of each child view
  873. * @param spans an empty array filled by this method with
  874. * values specifying the extent of each child view
  875. */
  876. protected void baselineLayout(int targetSpan, int axis, int[] offsets, int[] spans) {
  877. int totalAscent = (int)(targetSpan * getAlignment(axis));
  878. int totalDescent = targetSpan - totalAscent;
  879. int n = getViewCount();
  880. for (int i = 0; i < n; i++) {
  881. View v = getView(i);
  882. float align = v.getAlignment(axis);
  883. int viewSpan;
  884. if (v.getResizeWeight(axis) > 0) {
  885. // if resizable then resize to the best fit
  886. // the smallest span possible
  887. int minSpan = (int)v.getMinimumSpan(axis);
  888. // the largest span possible
  889. int maxSpan = (int)v.getMaximumSpan(axis);
  890. if (align == 0.0f) {
  891. // if the alignment is 0 then we need to fit into the descent
  892. viewSpan = Math.max(Math.min(maxSpan, totalDescent), minSpan);
  893. } else if (align == 1.0f) {
  894. // if the alignment is 1 then we need to fit into the ascent
  895. viewSpan = Math.max(Math.min(maxSpan, totalAscent), minSpan);
  896. } else {
  897. // figure out the span that we must fit into
  898. int fitSpan = (int)Math.min(totalAscent / align,
  899. totalDescent / (1.0f - align));
  900. // fit into the calculated span
  901. viewSpan = Math.max(Math.min(maxSpan, fitSpan), minSpan);
  902. }
  903. } else {
  904. // otherwise use the preferred spans
  905. viewSpan = (int)v.getPreferredSpan(axis);
  906. }
  907. offsets[i] = totalAscent - (int)(viewSpan * align);
  908. spans[i] = viewSpan;
  909. }
  910. }
  911. /**
  912. * Calculates the size requirements for this <code>BoxView</code>
  913. * by examining the size of each child view.
  914. *
  915. * @param axis the axis being studied
  916. * @param r the <code>SizeRequirements</code> object;
  917. * if <code>null</code> one will be created
  918. * @return the newly initialized <code>SizeRequirements</code> object
  919. */
  920. protected SizeRequirements baselineRequirements(int axis, SizeRequirements r) {
  921. SizeRequirements totalAscent = new SizeRequirements();
  922. SizeRequirements totalDescent = new SizeRequirements();
  923. if (r == null) {
  924. r = new SizeRequirements();
  925. }
  926. r.alignment = 0.5f;
  927. int n = getViewCount();
  928. // loop through all children calculating the max of all their ascents and
  929. // descents at minimum, preferred, and maximum sizes
  930. for (int i = 0; i < n; i++) {
  931. View v = getView(i);
  932. float align = v.getAlignment(axis);
  933. int span;
  934. int ascent;
  935. int descent;
  936. // find the maximum of the preferred ascents and descents
  937. span = (int)v.getPreferredSpan(axis);
  938. ascent = (int)(align * span);
  939. descent = span - ascent;
  940. totalAscent.preferred = Math.max(ascent, totalAscent.preferred);
  941. totalDescent.preferred = Math.max(descent, totalDescent.preferred);
  942. if (v.getResizeWeight(axis) > 0) {
  943. // if the view is resizable then do the same for the minimum and
  944. // maximum ascents and descents
  945. span = (int)v.getMinimumSpan(axis);
  946. ascent = (int)(align * span);
  947. descent = span - ascent;
  948. totalAscent.minimum = Math.max(ascent, totalAscent.minimum);
  949. totalDescent.minimum = Math.max(descent, totalDescent.minimum);
  950. span = (int)v.getMaximumSpan(axis);
  951. ascent = (int)(align * span);
  952. descent = span - ascent;
  953. totalAscent.maximum = Math.max(ascent, totalAscent.maximum);
  954. totalDescent.maximum = Math.max(descent, totalDescent.maximum);
  955. } else {
  956. // otherwise use the preferred
  957. totalAscent.minimum = Math.max(ascent, totalAscent.minimum);
  958. totalDescent.minimum = Math.max(descent, totalDescent.minimum);
  959. totalAscent.maximum = Math.max(ascent, totalAscent.maximum);
  960. totalDescent.maximum = Math.max(descent, totalDescent.maximum);
  961. }
  962. }
  963. // we now have an overall preferred, minimum, and maximum ascent and descent
  964. // calculate the preferred span as the sum of the preferred ascent and preferred descent
  965. r.preferred = (int)Math.min((long)totalAscent.preferred + (long)totalDescent.preferred,
  966. Integer.MAX_VALUE);
  967. // calculate the preferred alignment as the preferred ascent divided by the preferred span
  968. if (r.preferred > 0) {
  969. r.alignment = (float)totalAscent.preferred / r.preferred;
  970. }
  971. if (r.alignment == 0.0f) {
  972. // if the preferred alignment is 0 then the minimum and maximum spans are simply
  973. // the minimum and maximum descents since there's nothing above the baseline
  974. r.minimum = totalDescent.minimum;
  975. r.maximum = totalDescent.maximum;
  976. } else if (r.alignment == 1.0f) {
  977. // if the preferred alignment is 1 then the minimum and maximum spans are simply
  978. // the minimum and maximum ascents since there's nothing below the baseline
  979. r.minimum = totalAscent.minimum;
  980. r.maximum = totalAscent.maximum;
  981. } else {
  982. // we want to honor the preferred alignment so we calculate two possible minimum
  983. // span values using 1) the minimum ascent and the alignment, and 2) the minimum
  984. // descent and the alignment. We'll choose the larger of these two numbers.
  985. r.minimum = Math.max((int)(totalAscent.minimum / r.alignment),
  986. (int)(totalDescent.minimum / (1.0f - r.alignment)));
  987. // a similar calculation is made for the maximum but we choose the smaller number.
  988. r.maximum = Math.min((int)(totalAscent.maximum / r.alignment),
  989. (int)(totalDescent.maximum / (1.0f - r.alignment)));
  990. }
  991. return r;
  992. }
  993. /**
  994. * Fetches the offset of a particular child's current layout.
  995. * @param axis the axis being studied
  996. * @param childIndex the index of the requested child
  997. * @return the offset (location) for the specified child
  998. */
  999. protected int getOffset(int axis, int childIndex) {
  1000. int[] offsets = (axis == majorAxis) ? majorOffsets : minorOffsets;
  1001. return offsets[childIndex];
  1002. }
  1003. /**
  1004. * Fetches the span of a particular childs current layout.
  1005. * @param axis the axis being studied
  1006. * @param childIndex the index of the requested child
  1007. * @return the span (width or height) of the specified child
  1008. */
  1009. protected int getSpan(int axis, int childIndex) {
  1010. int[] spans = (axis == majorAxis) ? majorSpans : minorSpans;
  1011. return spans[childIndex];
  1012. }
  1013. /**
  1014. * Determines in which direction the next view lays.
  1015. * Consider the View at index n. Typically the <code>View</code>s
  1016. * are layed out from left to right, so that the <code>View</code>
  1017. * to the EAST will be at index n + 1, and the <code>View</code>
  1018. * to the WEST will be at index n - 1. In certain situations,
  1019. * such as with bidirectional text, it is possible
  1020. * that the <code>View</code> to EAST is not at index n + 1,
  1021. * but rather at index n - 1, or that the <code>View</code>
  1022. * to the WEST is not at index n - 1, but index n + 1.
  1023. * In this case this method would return true,
  1024. * indicating the <code>View</code>s are layed out in
  1025. * descending order. Otherwise the method would return false
  1026. * indicating the <code>View</code>s are layed out in ascending order.
  1027. * <p>
  1028. * If the receiver is laying its <code>View</code>s along the
  1029. * <code>Y_AXIS</code>, this will will return the value from
  1030. * invoking the same method on the <code>View</code>
  1031. * responsible for rendering <code>position</code> and
  1032. * <code>bias</code>. Otherwise this will return false.
  1033. *
  1034. * @param position position into the model
  1035. * @param bias either <code>Position.Bias.Forward</code> or
  1036. * <code>Position.Bias.Backward</code>
  1037. * @return true if the <code>View</code>s surrounding the
  1038. * <code>View</code> responding for rendering
  1039. * <code>position</code> and <code>bias</code>
  1040. * are layed out in descending order; otherwise false
  1041. */
  1042. protected boolean flipEastAndWestAtEnds(int position,
  1043. Position.Bias bias) {
  1044. if(majorAxis == Y_AXIS) {
  1045. int testPos = (bias == Position.Bias.Backward) ?
  1046. Math.max(0, position - 1) : position;
  1047. int index = getViewIndexAtPosition(testPos);
  1048. if(index != -1) {
  1049. View v = getView(index);
  1050. if(v != null && v instanceof CompositeView) {
  1051. return ((CompositeView)v).flipEastAndWestAtEnds(position,
  1052. bias);
  1053. }
  1054. }
  1055. }
  1056. return false;
  1057. }
  1058. // --- variables ------------------------------------------------
  1059. int majorAxis;
  1060. int majorSpan;
  1061. int minorSpan;
  1062. /*
  1063. * Request cache
  1064. */
  1065. boolean majorReqValid;
  1066. boolean minorReqValid;
  1067. SizeRequirements majorRequest;
  1068. SizeRequirements minorRequest;
  1069. /*
  1070. * Allocation cache
  1071. */
  1072. boolean majorAllocValid;
  1073. int[] majorOffsets;
  1074. int[] majorSpans;
  1075. boolean minorAllocValid;
  1076. int[] minorOffsets;
  1077. int[] minorSpans;
  1078. /** used in paint. */
  1079. Rectangle tempRect;
  1080. }