1. /*
  2. * @(#)BigBoxView.java 1.3 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.SizeRequirements;
  12. /**
  13. * The styled text support uses a tree of view objects to
  14. * represent the view of the model. This gives a number of
  15. * advantages over a monolithic formatter, but has a substantial
  16. * disadvantage as well. For large documents, a large number of
  17. * view objects will be created. To account for this, a view
  18. * implementation is needed that recognizes it is getting large,
  19. * and creates zones where it doesn't create the child view objects
  20. * until an attempt is made to display them. Box views are well suited
  21. * to this because:
  22. * <ul>
  23. * <li>
  24. * Boxes are a heavily used view, and having a box that
  25. * provides this behavior gives substantial opportunity
  26. * to plug the behavior into a view hierarchy from the
  27. * view factory.
  28. * <li>
  29. * Boxes are tiled in one direction, so it is easy to
  30. * divide them into zones in a reliable way.
  31. * <li>
  32. * Boxes have a simple relationship to the model (i.e. they
  33. * create child views that directly represent the child elements).
  34. * <li>
  35. * Boxes are easier to estimate the size of than some other shapes.
  36. * </ul>
  37. * <p>
  38. * BigBoxView extends BoxView to provide a box that implements
  39. * zones for it's children. The zones are special BoxView implementations
  40. * that don't necessarily create/store the children. The display
  41. * zones can be used to delay the creation of child views and/or to
  42. * reduce the amount of memory consumed by keeping zones built only if
  43. * there is viewing/editing activity in the zone.
  44. *
  45. * @author Timothy Prinzing
  46. * @version 1.3 11/29/01
  47. * @see View
  48. */
  49. /*public*/ class BigBoxView extends BoxView {
  50. int maxZoneSize = 8 * 1024;
  51. /**
  52. * Constructs a BigBoxView.
  53. *
  54. * @param elem the element this view is responsible for
  55. * @param axis either View.X_AXIS or View.Y_AXIS
  56. */
  57. public BigBoxView(Element elem, int axis) {
  58. super(elem, axis);
  59. }
  60. /**
  61. * Get the current maximum zone size.
  62. */
  63. public int getMaximumZoneSize() {
  64. return maxZoneSize;
  65. }
  66. /**
  67. * Set the desired maximum zone size. A
  68. * zone may get larger than this size if
  69. * a single child view is larger than this
  70. * size since zones are formed on child view
  71. * boundaries.
  72. *
  73. * @param size the number of characters the zone
  74. * may represent before attempting to break
  75. * the zone into a smaller size.
  76. */
  77. public void setMaximumZoneSize(int size) {
  78. maxZoneSize = size;
  79. }
  80. /**
  81. * Loads all of the children to initialize the view.
  82. * This is called by the <code>setParent</code> method.
  83. * This is reimplemented to not load any children directly
  84. * (as they are created by the zones). This method creates
  85. * the initial set of zones. Zones don't actually get
  86. * populated however until an attempt is made to display
  87. * them or to do model/view coordinate translation.
  88. *
  89. * @param f the view factory
  90. */
  91. protected void loadChildren(ViewFactory f) {
  92. // build the first zone.
  93. Document doc = getDocument();
  94. int offs0 = getStartOffset();
  95. int offs1 = getEndOffset();
  96. try {
  97. append(new Zone(getElement(),
  98. doc.createPosition(offs0),
  99. doc.createPosition(offs1)));
  100. handleInsert(offs0, offs1 - offs0);
  101. } catch (BadLocationException ble) {
  102. // this should puke in some way.
  103. }
  104. }
  105. /**
  106. * Fetches the child view index representing the given position in
  107. * the model.
  108. *
  109. * @param pos the position >= 0
  110. * @returns index of the view representing the given position, or
  111. * -1 if no view represents that position
  112. */
  113. protected int getViewIndexAtPosition(int pos) {
  114. // PENDING(prinz) this could be done as a binary
  115. // search, and probably should be.
  116. int n = getViewCount();
  117. if (pos == getEndOffset()) {
  118. return n - 1;
  119. }
  120. for(int i = 0; i < n; i++) {
  121. View v = getView(i);
  122. if(pos >= v.getStartOffset() &&
  123. pos < v.getEndOffset()) {
  124. return i;
  125. }
  126. }
  127. return -1;
  128. }
  129. void handleInsert(int pos, int length) {
  130. int index = getViewIndexAtPosition(pos);
  131. View v = getView(index);
  132. int offs0 = v.getStartOffset();
  133. int offs1 = v.getEndOffset();
  134. if ((offs1 - offs0) > maxZoneSize) {
  135. splitZone(index, offs0, offs1);
  136. }
  137. }
  138. void handleRemove(int pos, int length) {
  139. // IMPLEMENT
  140. }
  141. /**
  142. * Break up the zone at the given index into pieces
  143. * of an acceptable size.
  144. */
  145. void splitZone(int index, int offs0, int offs1) {
  146. // divide the old zone into a new set of bins
  147. try {
  148. Element elem = getElement();
  149. Document doc = elem.getDocument();
  150. Vector zones = new Vector();
  151. int offs = offs0;
  152. do {
  153. offs0 = offs;
  154. offs = Math.min(getDesiredZoneEnd(offs0), offs1);
  155. zones.addElement(new Zone(elem,
  156. doc.createPosition(offs0),
  157. doc.createPosition(offs)));
  158. } while (offs < offs1);
  159. View oldZone = getView(index);
  160. View[] newZones = new View[zones.size()];
  161. zones.copyInto(newZones);
  162. replace(index, 1, newZones);
  163. } catch (BadLocationException ble) {
  164. // puke in some way
  165. }
  166. // if the old zone was populated, transfer the contents
  167. // IMPLEMENT - we can cheat for now since they will
  168. // be rebuilt on demand.
  169. }
  170. /**
  171. * Returns the zone position to use for the
  172. * end of a zone that starts at the given
  173. * position. By default this returns something
  174. * close to half the max zone size.
  175. */
  176. int getDesiredZoneEnd(int pos) {
  177. Element elem = getElement();
  178. int index = elem.getElementIndex(pos + (maxZoneSize / 2));
  179. Element child = elem.getElement(index);
  180. int offs0 = child.getStartOffset();
  181. int offs1 = child.getEndOffset();
  182. if ((offs1 - pos) > maxZoneSize) {
  183. if (offs0 > pos) {
  184. return offs0;
  185. }
  186. }
  187. return offs1;
  188. }
  189. /**
  190. * The superclass behavior will try to update the child views
  191. * which is not desired in this case, since the children are
  192. * zones and not directly effected by the changes to the
  193. * associated element. This is reimplemented to do nothing.
  194. */
  195. protected boolean updateChildren(DocumentEvent.ElementChange ec,
  196. DocumentEvent e, ViewFactory f) {
  197. return false;
  198. }
  199. // ---- View methods ----------------------------------------------------
  200. /**
  201. * Gives notification that something was inserted into the document
  202. * in a location that this view is responsible for. This is largely
  203. * delegated to the superclass, but is reimplemented to update the
  204. * relevant zone (i.e. determine if a zone needs to be split into a
  205. * set of 2 or more zones).
  206. *
  207. * @param changes the change information from the associated document
  208. * @param a the current allocation of the view
  209. * @param f the factory to use to rebuild if the view has children
  210. * @see View#insertUpdate
  211. */
  212. public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
  213. handleInsert(changes.getOffset(), changes.getLength());
  214. super.insertUpdate(changes, a, f);
  215. }
  216. /**
  217. * Gives notification that something was removed from the document
  218. * in a location that this view is responsible for. This is largely
  219. * delegated to the superclass, but is reimplemented to update the
  220. * relevant zones (i.e. determine if zones need to be removed or
  221. * joined with another zone).
  222. *
  223. * @param changes the change information from the associated document
  224. * @param a the current allocation of the view
  225. * @param f the factory to use to rebuild if the view has children
  226. * @see View#removeUpdate
  227. */
  228. public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
  229. handleRemove(changes.getOffset(), changes.getLength());
  230. super.removeUpdate(changes, a, f);
  231. }
  232. /**
  233. * Internally created view that has the purpose of holding
  234. * the views that represent the children of the BigBoxView
  235. * that have been arranged in a zone.
  236. */
  237. class Zone extends BoxView {
  238. Position start;
  239. Position end;
  240. /**
  241. * Last allocated span along the minor axis.
  242. * This is used to estimate the volume of
  243. * the zone while waiting to populate it.
  244. */
  245. int minorSpan = 500;
  246. /**
  247. * Value used to guess what volume the zone
  248. * would occupy if it was in fact loaded.
  249. * The default number is a wild guess of a
  250. * font taking 8x12 pixels and occupying less
  251. * than half the space in the display.
  252. */
  253. float volumeCoefficient = (8 * 12) * 2.5f;
  254. Zone(Element elem, Position start, Position end) {
  255. super(elem, BigBoxView.this.getAxis());
  256. this.start = start;
  257. this.end = end;
  258. }
  259. /**
  260. * Creates the child views and populates the
  261. * zone with them. This is done by translating
  262. * the positions to child element index locations
  263. * and building views to those elements.
  264. */
  265. public void load() {
  266. Element e = getElement();
  267. ViewFactory f = getViewFactory();
  268. int index0 = e.getElementIndex(getStartOffset());
  269. int index1 = e.getElementIndex(getEndOffset());
  270. View[] added = new View[index1 - index0 + 1];
  271. for (int i = index0; i <= index1; i++) {
  272. added[i - index0] = f.create(e.getElement(i));
  273. }
  274. replace(0, 0, added);
  275. }
  276. /**
  277. * Removes the child views and returns to a
  278. * state of unloaded.
  279. */
  280. public void unload() {
  281. removeAll();
  282. }
  283. /**
  284. * Determines if the zone is in the loaded state
  285. * or not.
  286. */
  287. public boolean isLoaded() {
  288. return (getViewCount() != 0);
  289. }
  290. /**
  291. * This is reimplemented to do nothing since the
  292. * children are created when the zone is loaded
  293. * rather then when it is placed in the view
  294. * hierarchy.
  295. */
  296. protected void loadChildren(ViewFactory f) {
  297. }
  298. /**
  299. * Fetches the child view index representing the given position in
  300. * the model. Since the zone contains a cluster of the overall
  301. * set of child elements, we can determine the index fairly
  302. * quickly from the model by subtracting the index of the
  303. * start offset from the index of the position given.
  304. *
  305. * @param pos the position >= 0
  306. * @returns index of the view representing the given position, or
  307. * -1 if no view represents that position
  308. */
  309. protected int getViewIndexAtPosition(int pos) {
  310. Element elem = getElement();
  311. int index1 = elem.getElementIndex(pos);
  312. int index0 = elem.getElementIndex(getStartOffset());
  313. return index1 - index0;
  314. }
  315. /**
  316. * Performs layout of the children. The size is the
  317. * area inside of the insets.
  318. *
  319. * @param width the width >= 0
  320. * @param height the height >= 0
  321. */
  322. protected void layout(int width, int height) {
  323. if (isLoaded()) {
  324. super.layout(width, height);
  325. } else {
  326. int axis = getAxis();
  327. int newSpan = (axis == Y_AXIS) ? width : height;
  328. if (newSpan != minorSpan) {
  329. minorSpan = newSpan;
  330. // notify preference change along the major axis
  331. preferenceChanged(null, (axis == X_AXIS), (axis == Y_AXIS));
  332. }
  333. }
  334. }
  335. protected boolean updateChildren(DocumentEvent.ElementChange ec,
  336. DocumentEvent e, ViewFactory f) {
  337. // the structure of this element changed.
  338. Element[] removedElems = ec.getChildrenRemoved();
  339. Element[] addedElems = ec.getChildrenAdded();
  340. Element elem = getElement();
  341. int index0 = elem.getElementIndex(getStartOffset());
  342. int index1 = elem.getElementIndex(getEndOffset()-1);
  343. int index = ec.getIndex();
  344. if ((index >= index0) && (index <= index1)) {
  345. // The change is in this zone
  346. int replaceIndex = index - index0;
  347. int nadd = Math.min(index1 - index0 + 1, addedElems.length);
  348. int nremove = Math.min(index1 - index0 + 1, removedElems.length);
  349. View[] added = new View[nadd];
  350. for (int i = 0; i < nadd; i++) {
  351. added[i] = f.create(addedElems[i]);
  352. }
  353. replace(replaceIndex, nremove, added);
  354. }
  355. return true;
  356. }
  357. // --- View methods ----------------------------------
  358. /**
  359. * Fetches the attributes to use when rendering. This view
  360. * isn't directly responsible for an element so it returns
  361. * the outer classes attributes.
  362. */
  363. public AttributeSet getAttributes() {
  364. return BigBoxView.this.getAttributes();
  365. }
  366. /**
  367. * Determines the preferred span for this view along an
  368. * axis.
  369. *
  370. * @param axis may be either View.X_AXIS or View.Y_AXIS
  371. * @returns the span the view would like to be rendered into.
  372. * Typically the view is told to render into the span
  373. * that is returned, although there is no guarantee.
  374. * The parent may choose to resize or break the view.
  375. * @see View#getPreferredSpan
  376. */
  377. public float getPreferredSpan(int axis) {
  378. if (isLoaded()) {
  379. return super.getPreferredSpan(axis);
  380. }
  381. if (getAxis() == axis) {
  382. // major axis
  383. int charVolume = getEndOffset() - getStartOffset();
  384. float displayVolume = charVolume * volumeCoefficient;
  385. float majorSpan = displayVolume / minorSpan;
  386. return majorSpan;
  387. }
  388. // minor axis
  389. return minorSpan;
  390. }
  391. /**
  392. * Determines the minimum span for this view along an
  393. * axis.
  394. *
  395. * @param axis may be either View.X_AXIS or View.Y_AXIS
  396. * @returns the minimum span the view can be rendered into.
  397. * @see View#getPreferredSpan
  398. */
  399. public float getMinimumSpan(int axis) {
  400. if (isLoaded()) {
  401. return super.getMinimumSpan(axis);
  402. }
  403. if (axis == getAxis()) {
  404. // major axis estimate is rigid
  405. return getPreferredSpan(axis);
  406. }
  407. // minor axis estimate is flexible
  408. if (axis == X_AXIS) {
  409. return getLeftInset() + getRightInset() + 1;
  410. }
  411. return getTopInset() + getBottomInset() + 1;
  412. }
  413. /**
  414. * Determines the maximum span for this view along an
  415. * axis.
  416. *
  417. * @param axis may be either View.X_AXIS or View.Y_AXIS
  418. * @returns the maximum span the view can be rendered into.
  419. * @see View#getPreferredSpan
  420. */
  421. public float getMaximumSpan(int axis) {
  422. if (isLoaded()) {
  423. return super.getMaximumSpan(axis);
  424. }
  425. if (axis == getAxis()) {
  426. // major axis estimate is rigid
  427. return getPreferredSpan(axis);
  428. }
  429. // minor axis estimate is flexible
  430. return (Integer.MAX_VALUE / 1000);
  431. }
  432. /**
  433. * Determines the desired alignment for this view along an
  434. * axis. By default this is simply centered.
  435. *
  436. * @param axis may be either View.X_AXIS or View.Y_AXIS
  437. * @returns The desired alignment. This should be a value
  438. * >= 0.0 and <= 1.0 where 0 indicates alignment at the
  439. * origin and 1.0 indicates alignment to the full span
  440. * away from the origin. An alignment of 0.5 would be the
  441. * center of the view.
  442. */
  443. public float getAlignment(int axis) {
  444. if (isLoaded()) {
  445. return super.getAlignment(axis);
  446. }
  447. return 0.5f;
  448. }
  449. /**
  450. * Provides a mapping from the view coordinate space to the logical
  451. * coordinate space of the model. This is implemented to first
  452. * make sure the zone is loaded before providing the superclass
  453. * behavior.
  454. *
  455. * @param x x coordinate of the view location to convert >= 0
  456. * @param y y coordinate of the view location to convert >= 0
  457. * @param a the allocated region to render into
  458. * @return the location within the model that best represents the
  459. * given point in the view >= 0
  460. * @see View#viewToModel
  461. */
  462. public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
  463. load();
  464. return super.viewToModel(x, y, a, bias);
  465. }
  466. /**
  467. * Provides a mapping from the document model coordinate space
  468. * to the coordinate space of the view mapped to it. This is
  469. * implemented to provide the superclass behavior after first
  470. * making sure the zone is loaded (The zone must be loaded to
  471. * make this calculation).
  472. *
  473. * @param pos the position to convert
  474. * @param a the allocated region to render into
  475. * @return the bounding box of the given position
  476. * @exception BadLocationException if the given position does not represent a
  477. * valid location in the associated document
  478. * @see View#modelToView
  479. */
  480. public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
  481. load();
  482. return super.modelToView(pos, a, b);
  483. }
  484. /**
  485. * Start of the zones range.
  486. *
  487. * @see View#getStartOffset
  488. */
  489. public int getStartOffset() {
  490. return start.getOffset();
  491. }
  492. /**
  493. * End of the zones range.
  494. */
  495. public int getEndOffset() {
  496. return end.getOffset();
  497. }
  498. /**
  499. * Gives notification that something was inserted into
  500. * the document in a location that this view is responsible for.
  501. * If the zone has been loaded, the superclass behavior is
  502. * invoked, otherwise this does nothing.
  503. *
  504. * @param e the change information from the associated document
  505. * @param a the current allocation of the view
  506. * @param f the factory to use to rebuild if the view has children
  507. * @see View#insertUpdate
  508. */
  509. public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  510. if (isLoaded()) {
  511. super.insertUpdate(e, a, f);
  512. }
  513. }
  514. /**
  515. * Gives notification that something was removed from the document
  516. * in a location that this view is responsible for.
  517. * If the zone has been loaded, the superclass behavior is
  518. * invoked, otherwise this does nothing.
  519. *
  520. * @param e the change information from the associated document
  521. * @param a the current allocation of the view
  522. * @param f the factory to use to rebuild if the view has children
  523. * @see View#removeUpdate
  524. */
  525. public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  526. if (isLoaded()) {
  527. super.removeUpdate(e, a, f);
  528. }
  529. }
  530. /**
  531. * Gives notification from the document that attributes were changed
  532. * in a location that this view is responsible for.
  533. * If the zone has been loaded, the superclass behavior is
  534. * invoked, otherwise this does nothing.
  535. *
  536. * @param e the change information from the associated document
  537. * @param a the current allocation of the view
  538. * @param f the factory to use to rebuild if the view has children
  539. * @see View#removeUpdate
  540. */
  541. public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  542. if (isLoaded()) {
  543. super.changedUpdate(e, a, f);
  544. }
  545. }
  546. }
  547. }