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