1. /*
  2. * @(#)ZoneView.java 1.6 00/02/02
  3. *
  4. * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
  5. *
  6. * This software is the proprietary information of Sun Microsystems, Inc.
  7. * Use is subject to license terms.
  8. *
  9. */
  10. package javax.swing.text;
  11. import java.util.Vector;
  12. import java.awt.*;
  13. import javax.swing.event.*;
  14. import javax.swing.SizeRequirements;
  15. /**
  16. * ZoneView is a View implementation that creates zones for which
  17. * the child views are not created or stored until they are needed
  18. * for display or model/view translations. This enables a substantial
  19. * reduction in memory consumption for situations where the model
  20. * being represented is very large, by building view objects only for
  21. * the region being actively viewed/edited. The size of the children
  22. * can be estimated in some way, or calculated asynchronously with
  23. * only the result being saved.
  24. * <p>
  25. * ZoneView extends BoxView to provide a box that implements
  26. * zones for it's children. The zones are special View implementations
  27. * (the children of an instance of this class) that represent only a
  28. * portion of the model that an instance of ZoneView is responsible
  29. * for. The zones don't create child views until an attempt is made
  30. * to display them. A box shaped view is well suited to this because:
  31. * <ul>
  32. * <li>
  33. * Boxes are a heavily used view, and having a box that
  34. * provides this behavior gives substantial opportunity
  35. * to plug the behavior into a view hierarchy from the
  36. * view factory.
  37. * <li>
  38. * Boxes are tiled in one direction, so it is easy to
  39. * divide them into zones in a reliable way.
  40. * <li>
  41. * Boxes typically have a simple relationship to the model (i.e. they
  42. * create child views that directly represent the child elements).
  43. * <li>
  44. * Boxes are easier to estimate the size of than some other shapes.
  45. * </ul>
  46. * <p>
  47. * The default behavior is controled by two properties, maxZoneSize
  48. * and maxZonesLoaded. Setting maxZoneSize to Integer.MAX_VALUE would
  49. * have the effect of causing only one zone to be created. This would
  50. * effectively turn the view into an implementation of the decorator
  51. * pattern. Setting maxZonesLoaded to a value of Integer.MAX_VALUE would
  52. * cause zones to never be unloaded. For simplicity, zones are created on
  53. * boundaries represented by the child elements of the element the view is
  54. * responsible for. The zones can be any View implementation, but the
  55. * default implementation is based upon AsyncBoxView which supports fairly
  56. * large zones efficiently.
  57. *
  58. * @author Timothy Prinzing
  59. * @version 1.6 02/02/00
  60. * @see View
  61. * @since 1.3
  62. */
  63. public class ZoneView extends BoxView {
  64. int maxZoneSize = 8 * 1024;
  65. int maxZonesLoaded = 3;
  66. /**
  67. * Constructs a ZoneView.
  68. *
  69. * @param elem the element this view is responsible for
  70. * @param axis either View.X_AXIS or View.Y_AXIS
  71. */
  72. public ZoneView(Element elem, int axis) {
  73. super(elem, axis);
  74. }
  75. /**
  76. * Get the current maximum zone size.
  77. */
  78. public int getMaximumZoneSize() {
  79. return maxZoneSize;
  80. }
  81. /**
  82. * Set the desired maximum zone size. A
  83. * zone may get larger than this size if
  84. * a single child view is larger than this
  85. * size since zones are formed on child view
  86. * boundaries.
  87. *
  88. * @param size the number of characters the zone
  89. * may represent before attempting to break
  90. * the zone into a smaller size.
  91. */
  92. public void setMaximumZoneSize(int size) {
  93. maxZoneSize = size;
  94. }
  95. /**
  96. * Get the current setting of the number of zones
  97. * allowed to be loaded at the same time.
  98. */
  99. public int getMaxZonesLoaded() {
  100. return maxZonesLoaded;
  101. }
  102. /**
  103. * Set the current setting of the number of zones
  104. * allowed to be loaded at the same time.
  105. *
  106. * @param mzl the desired maximum number of zones
  107. * to be actively loaded.
  108. */
  109. public void setMaxZonesLoaded(int mzl) {
  110. maxZonesLoaded = mzl;
  111. }
  112. /**
  113. * Called by a zone when it gets loaded. This happens when
  114. * an attempt is made to display or perform a model/view
  115. * translation on a zone that was in an unloaded state.
  116. * This is imlemented to check if the maximum number of
  117. * zones was reached and to unload the oldest zone if so.
  118. *
  119. * @param zone the child view that was just loaded.
  120. */
  121. protected void zoneWasLoaded(View zone) {
  122. // TBD
  123. }
  124. /**
  125. * Unload a zone (Convert the zone to its memory saving state).
  126. * The zones are expected to represent a subset of the
  127. * child elements of the element this view is responsible for.
  128. * Therefore, the default implementation is to simple remove
  129. * all the children.
  130. *
  131. * @param zone the child view desired to be set to an
  132. * unloaded state.
  133. */
  134. protected void unloadZone(View zone) {
  135. zone.removeAll();
  136. }
  137. /**
  138. * Determine if a zone is in the loaded state.
  139. * The zones are expected to represent a subset of the
  140. * child elements of the element this view is responsible for.
  141. * Therefore, the default implementation is to return
  142. * true if the view has children.
  143. */
  144. protected boolean isZoneLoaded(View zone) {
  145. return (zone.getViewCount() > 0);
  146. }
  147. /**
  148. * Create a view to represent a zone for the given
  149. * range within the model (which should be within
  150. * the range of this objects responsibility). This
  151. * is called by the zone management logic to create
  152. * new zones. Subclasses can provide a different
  153. * implementation for a zone by changing this method.
  154. *
  155. * @param p0 the start of the desired zone. This should
  156. * be >= getStartOffset() and < getEndOffset(). This
  157. * value should also be < p1.
  158. * @param p1 the end of the desired zone. This should
  159. * be > getStartOffset() and <= getEndOffset(). This
  160. * value should also be > p0.
  161. */
  162. protected View createZone(int p0, int p1) {
  163. Document doc = getDocument();
  164. View zone = null;
  165. try {
  166. zone = new Zone(getElement(),
  167. doc.createPosition(p0),
  168. doc.createPosition(p1));
  169. } catch (BadLocationException ble) {
  170. // this should puke in some way.
  171. throw new StateInvariantError(ble.getMessage());
  172. }
  173. return zone;
  174. }
  175. /**
  176. * Loads all of the children to initialize the view.
  177. * This is called by the <code>setParent</code> method.
  178. * This is reimplemented to not load any children directly
  179. * (as they are created by the zones). This method creates
  180. * the initial set of zones. Zones don't actually get
  181. * populated however until an attempt is made to display
  182. * them or to do model/view coordinate translation.
  183. *
  184. * @param f the view factory
  185. */
  186. protected void loadChildren(ViewFactory f) {
  187. // build the first zone.
  188. Document doc = getDocument();
  189. int offs0 = getStartOffset();
  190. int offs1 = getEndOffset();
  191. append(createZone(offs0, offs1));
  192. handleInsert(offs0, offs1 - offs0);
  193. }
  194. /**
  195. * Fetches the child view index representing the given position in
  196. * the model.
  197. *
  198. * @param pos the position >= 0
  199. * @returns index of the view representing the given position, or
  200. * -1 if no view represents that position
  201. */
  202. protected int getViewIndexAtPosition(int pos) {
  203. // PENDING(prinz) this could be done as a binary
  204. // search, and probably should be.
  205. int n = getViewCount();
  206. if (pos == getEndOffset()) {
  207. return n - 1;
  208. }
  209. for(int i = 0; i < n; i++) {
  210. View v = getView(i);
  211. if(pos >= v.getStartOffset() &&
  212. pos < v.getEndOffset()) {
  213. return i;
  214. }
  215. }
  216. return -1;
  217. }
  218. void handleInsert(int pos, int length) {
  219. int index = getViewIndexAtPosition(pos);
  220. View v = getView(index);
  221. int offs0 = v.getStartOffset();
  222. int offs1 = v.getEndOffset();
  223. if ((offs1 - offs0) > maxZoneSize) {
  224. splitZone(index, offs0, offs1);
  225. }
  226. }
  227. void handleRemove(int pos, int length) {
  228. // IMPLEMENT
  229. }
  230. /**
  231. * Break up the zone at the given index into pieces
  232. * of an acceptable size.
  233. */
  234. void splitZone(int index, int offs0, int offs1) {
  235. // divide the old zone into a new set of bins
  236. Element elem = getElement();
  237. Document doc = elem.getDocument();
  238. Vector zones = new Vector();
  239. int offs = offs0;
  240. do {
  241. offs0 = offs;
  242. offs = Math.min(getDesiredZoneEnd(offs0), offs1);
  243. zones.addElement(createZone(offs0, offs));
  244. } while (offs < offs1);
  245. View oldZone = getView(index);
  246. View[] newZones = new View[zones.size()];
  247. zones.copyInto(newZones);
  248. replace(index, 1, newZones);
  249. }
  250. /**
  251. * Returns the zone position to use for the
  252. * end of a zone that starts at the given
  253. * position. By default this returns something
  254. * close to half the max zone size.
  255. */
  256. int getDesiredZoneEnd(int pos) {
  257. Element elem = getElement();
  258. int index = elem.getElementIndex(pos + (maxZoneSize / 2));
  259. Element child = elem.getElement(index);
  260. int offs0 = child.getStartOffset();
  261. int offs1 = child.getEndOffset();
  262. if ((offs1 - pos) > maxZoneSize) {
  263. if (offs0 > pos) {
  264. return offs0;
  265. }
  266. }
  267. return offs1;
  268. }
  269. // ---- View methods ----------------------------------------------------
  270. /**
  271. * The superclass behavior will try to update the child views
  272. * which is not desired in this case, since the children are
  273. * zones and not directly effected by the changes to the
  274. * associated element. This is reimplemented to do nothing
  275. * and return false.
  276. */
  277. protected boolean updateChildren(DocumentEvent.ElementChange ec,
  278. DocumentEvent e, ViewFactory f) {
  279. return false;
  280. }
  281. /**
  282. * Gives notification that something was inserted into the document
  283. * in a location that this view is responsible for. This is largely
  284. * delegated to the superclass, but is reimplemented to update the
  285. * relevant zone (i.e. determine if a zone needs to be split into a
  286. * set of 2 or more zones).
  287. *
  288. * @param changes 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 View#insertUpdate
  292. */
  293. public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
  294. handleInsert(changes.getOffset(), changes.getLength());
  295. super.insertUpdate(changes, a, f);
  296. }
  297. /**
  298. * Gives notification that something was removed from the document
  299. * in a location that this view is responsible for. This is largely
  300. * delegated to the superclass, but is reimplemented to update the
  301. * relevant zones (i.e. determine if zones need to be removed or
  302. * joined with another zone).
  303. *
  304. * @param changes the change information from the associated document
  305. * @param a the current allocation of the view
  306. * @param f the factory to use to rebuild if the view has children
  307. * @see View#removeUpdate
  308. */
  309. public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
  310. handleRemove(changes.getOffset(), changes.getLength());
  311. super.removeUpdate(changes, a, f);
  312. }
  313. /**
  314. * Internally created view that has the purpose of holding
  315. * the views that represent the children of the ZoneView
  316. * that have been arranged in a zone.
  317. *
  318. * PENDING(prinz) should be based upon AsyncBoxView to
  319. * match documentation. Also, the size estimation is
  320. * completely bogus at the moment. Fixme before release!!
  321. */
  322. class Zone extends BoxView {
  323. Position start;
  324. Position end;
  325. /**
  326. * Last allocated span along the minor axis.
  327. * This is used to estimate the volume of
  328. * the zone while waiting to populate it.
  329. */
  330. int minorSpan = 500;
  331. /**
  332. * Value used to guess what volume the zone
  333. * would occupy if it was in fact loaded.
  334. * The default number is a wild guess of a
  335. * font taking 8x12 pixels and occupying less
  336. * than half the space in the display.
  337. */
  338. float volumeCoefficient = (8 * 12) * 2.5f;
  339. Zone(Element elem, Position start, Position end) {
  340. super(elem, ZoneView.this.getAxis());
  341. this.start = start;
  342. this.end = end;
  343. }
  344. /**
  345. * Creates the child views and populates the
  346. * zone with them. This is done by translating
  347. * the positions to child element index locations
  348. * and building views to those elements. If the
  349. * zone is already loaded, this does nothing.
  350. */
  351. public void load() {
  352. if (! isLoaded()) {
  353. System.out.println("loading: " + getStartOffset() + "," +
  354. getEndOffset());
  355. Element e = getElement();
  356. ViewFactory f = getViewFactory();
  357. int index0 = e.getElementIndex(getStartOffset());
  358. int index1 = e.getElementIndex(getEndOffset());
  359. View[] added = new View[index1 - index0 + 1];
  360. for (int i = index0; i <= index1; i++) {
  361. added[i - index0] = f.create(e.getElement(i));
  362. }
  363. replace(0, 0, added);
  364. }
  365. }
  366. /**
  367. * Removes the child views and returns to a
  368. * state of unloaded.
  369. */
  370. public void unload() {
  371. removeAll();
  372. }
  373. /**
  374. * Determines if the zone is in the loaded state
  375. * or not.
  376. */
  377. public boolean isLoaded() {
  378. return (getViewCount() != 0);
  379. }
  380. /**
  381. * This is reimplemented to do nothing since the
  382. * children are created when the zone is loaded
  383. * rather then when it is placed in the view
  384. * hierarchy.
  385. */
  386. protected void loadChildren(ViewFactory f) {
  387. }
  388. /**
  389. * Fetches the child view index representing the given position in
  390. * the model. Since the zone contains a cluster of the overall
  391. * set of child elements, we can determine the index fairly
  392. * quickly from the model by subtracting the index of the
  393. * start offset from the index of the position given.
  394. *
  395. * @param pos the position >= 0
  396. * @returns index of the view representing the given position, or
  397. * -1 if no view represents that position
  398. */
  399. protected int getViewIndexAtPosition(int pos) {
  400. Element elem = getElement();
  401. int index1 = elem.getElementIndex(pos);
  402. int index0 = elem.getElementIndex(getStartOffset());
  403. return index1 - index0;
  404. }
  405. /**
  406. * Performs layout of the children. The size is the
  407. * area inside of the insets.
  408. *
  409. * @param width the width >= 0
  410. * @param height the height >= 0
  411. */
  412. protected void layout(int width, int height) {
  413. if (isLoaded()) {
  414. super.layout(width, height);
  415. } else {
  416. int axis = getAxis();
  417. int newSpan = (axis == Y_AXIS) ? width : height;
  418. if (newSpan != minorSpan) {
  419. minorSpan = newSpan;
  420. // notify preference change along the major axis
  421. preferenceChanged(null, (axis == X_AXIS), (axis == Y_AXIS));
  422. }
  423. }
  424. }
  425. protected boolean updateChildren(DocumentEvent.ElementChange ec,
  426. DocumentEvent e, ViewFactory f) {
  427. // the structure of this element changed.
  428. Element[] removedElems = ec.getChildrenRemoved();
  429. Element[] addedElems = ec.getChildrenAdded();
  430. Element elem = getElement();
  431. int index0 = elem.getElementIndex(getStartOffset());
  432. int index1 = elem.getElementIndex(getEndOffset()-1);
  433. int index = ec.getIndex();
  434. if ((index >= index0) && (index <= index1)) {
  435. // The change is in this zone
  436. int replaceIndex = index - index0;
  437. int nadd = Math.min(index1 - index0 + 1, addedElems.length);
  438. int nremove = Math.min(index1 - index0 + 1, removedElems.length);
  439. View[] added = new View[nadd];
  440. for (int i = 0; i < nadd; i++) {
  441. added[i] = f.create(addedElems[i]);
  442. }
  443. replace(replaceIndex, nremove, added);
  444. }
  445. return true;
  446. }
  447. // --- View methods ----------------------------------
  448. /**
  449. * Fetches the attributes to use when rendering. This view
  450. * isn't directly responsible for an element so it returns
  451. * the outer classes attributes.
  452. */
  453. public AttributeSet getAttributes() {
  454. return ZoneView.this.getAttributes();
  455. }
  456. /**
  457. * Determines the preferred span for this view along an
  458. * axis.
  459. *
  460. * @param axis may be either View.X_AXIS or View.Y_AXIS
  461. * @returns the span the view would like to be rendered into.
  462. * Typically the view is told to render into the span
  463. * that is returned, although there is no guarantee.
  464. * The parent may choose to resize or break the view.
  465. * @see View#getPreferredSpan
  466. */
  467. public float getPreferredSpan(int axis) {
  468. if (isLoaded()) {
  469. return super.getPreferredSpan(axis);
  470. }
  471. if (getAxis() == axis) {
  472. // major axis
  473. int charVolume = getEndOffset() - getStartOffset();
  474. float displayVolume = charVolume * volumeCoefficient;
  475. float majorSpan = displayVolume / minorSpan;
  476. return majorSpan;
  477. }
  478. // minor axis
  479. return minorSpan;
  480. }
  481. /**
  482. * Determines the minimum span for this view along an
  483. * axis.
  484. *
  485. * @param axis may be either View.X_AXIS or View.Y_AXIS
  486. * @returns the minimum span the view can be rendered into.
  487. * @see View#getPreferredSpan
  488. */
  489. public float getMinimumSpan(int axis) {
  490. if (isLoaded()) {
  491. return super.getMinimumSpan(axis);
  492. }
  493. if (axis == getAxis()) {
  494. // major axis estimate is rigid
  495. return getPreferredSpan(axis);
  496. }
  497. // minor axis estimate is flexible
  498. if (axis == X_AXIS) {
  499. return getLeftInset() + getRightInset() + 1;
  500. }
  501. return getTopInset() + getBottomInset() + 1;
  502. }
  503. /**
  504. * Determines the maximum span for this view along an
  505. * axis.
  506. *
  507. * @param axis may be either View.X_AXIS or View.Y_AXIS
  508. * @returns the maximum span the view can be rendered into.
  509. * @see View#getPreferredSpan
  510. */
  511. public float getMaximumSpan(int axis) {
  512. if (isLoaded()) {
  513. return super.getMaximumSpan(axis);
  514. }
  515. if (axis == getAxis()) {
  516. // major axis estimate is rigid
  517. return getPreferredSpan(axis);
  518. }
  519. // minor axis estimate is flexible
  520. return (Integer.MAX_VALUE / 1000);
  521. }
  522. /**
  523. * Determines the desired alignment for this view along an
  524. * axis. By default this is simply centered.
  525. *
  526. * @param axis may be either View.X_AXIS or View.Y_AXIS
  527. * @returns The desired alignment. This should be a value
  528. * >= 0.0 and <= 1.0 where 0 indicates alignment at the
  529. * origin and 1.0 indicates alignment to the full span
  530. * away from the origin. An alignment of 0.5 would be the
  531. * center of the view.
  532. */
  533. public float getAlignment(int axis) {
  534. if (isLoaded()) {
  535. return super.getAlignment(axis);
  536. }
  537. return 0.5f;
  538. }
  539. /**
  540. * Renders using the given rendering surface and area on that
  541. * surface. This is implemented to load the zone if its not
  542. * already loaded, and then perform the superclass behavior.
  543. *
  544. * @param g the rendering surface to use
  545. * @param a the allocated region to render into
  546. * @see View#paint
  547. */
  548. public void paint(Graphics g, Shape a) {
  549. load();
  550. super.paint(g, a);
  551. }
  552. /**
  553. * Provides a mapping from the view coordinate space to the logical
  554. * coordinate space of the model. This is implemented to first
  555. * make sure the zone is loaded before providing the superclass
  556. * behavior.
  557. *
  558. * @param x x coordinate of the view location to convert >= 0
  559. * @param y y coordinate of the view location to convert >= 0
  560. * @param a the allocated region to render into
  561. * @return the location within the model that best represents the
  562. * given point in the view >= 0
  563. * @see View#viewToModel
  564. */
  565. public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
  566. load();
  567. return super.viewToModel(x, y, a, bias);
  568. }
  569. /**
  570. * Provides a mapping from the document model coordinate space
  571. * to the coordinate space of the view mapped to it. This is
  572. * implemented to provide the superclass behavior after first
  573. * making sure the zone is loaded (The zone must be loaded to
  574. * make this calculation).
  575. *
  576. * @param pos the position to convert
  577. * @param a the allocated region to render into
  578. * @return the bounding box of the given position
  579. * @exception BadLocationException if the given position does not represent a
  580. * valid location in the associated document
  581. * @see View#modelToView
  582. */
  583. public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
  584. load();
  585. return super.modelToView(pos, a, b);
  586. }
  587. /**
  588. * Start of the zones range.
  589. *
  590. * @see View#getStartOffset
  591. */
  592. public int getStartOffset() {
  593. return start.getOffset();
  594. }
  595. /**
  596. * End of the zones range.
  597. */
  598. public int getEndOffset() {
  599. return end.getOffset();
  600. }
  601. /**
  602. * Gives notification that something was inserted into
  603. * the document in a location that this view is responsible for.
  604. * If the zone has been loaded, the superclass behavior is
  605. * invoked, otherwise this does nothing.
  606. *
  607. * @param e the change information from the associated document
  608. * @param a the current allocation of the view
  609. * @param f the factory to use to rebuild if the view has children
  610. * @see View#insertUpdate
  611. */
  612. public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  613. if (isLoaded()) {
  614. super.insertUpdate(e, a, f);
  615. }
  616. }
  617. /**
  618. * Gives notification that something was removed from the document
  619. * in a location that this view is responsible for.
  620. * If the zone has been loaded, the superclass behavior is
  621. * invoked, otherwise this does nothing.
  622. *
  623. * @param e the change information from the associated document
  624. * @param a the current allocation of the view
  625. * @param f the factory to use to rebuild if the view has children
  626. * @see View#removeUpdate
  627. */
  628. public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  629. if (isLoaded()) {
  630. super.removeUpdate(e, a, f);
  631. }
  632. }
  633. /**
  634. * Gives notification from the document that attributes were changed
  635. * in a location that this view is responsible for.
  636. * If the zone has been loaded, the superclass behavior is
  637. * invoked, otherwise this does nothing.
  638. *
  639. * @param e the change information from the associated document
  640. * @param a the current allocation of the view
  641. * @param f the factory to use to rebuild if the view has children
  642. * @see View#removeUpdate
  643. */
  644. public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  645. if (isLoaded()) {
  646. super.changedUpdate(e, a, f);
  647. }
  648. }
  649. }
  650. }