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