001 /* ZoneView.java -- An effective BoxView subclass
002 Copyright (C) 2006 Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 package javax.swing.text;
040
041 import java.awt.Shape;
042 import java.util.ArrayList;
043 import java.util.LinkedList;
044
045 import javax.swing.event.DocumentEvent;
046
047 /**
048 * A View implementation that delays loading of sub views until they are
049 * needed for display or internal transformations. This can be used for
050 * editors that need to handle large documents more effectivly than the
051 * standard {@link BoxView}.
052 *
053 * @author Roman Kennke (kennke@aicas.com)
054 *
055 * @since 1.3
056 */
057 public class ZoneView
058 extends BoxView
059 {
060
061 /**
062 * The default zone view implementation. The specs suggest that this is
063 * a subclass of AsyncBoxView, so do we.
064 */
065 static class Zone
066 extends AsyncBoxView
067 {
068 /**
069 * The start position for this zone.
070 */
071 private Position p0;
072
073 /**
074 * The end position for this zone.
075 */
076 private Position p1;
077
078 /**
079 * Creates a new Zone for the specified element, start and end positions.
080 *
081 * @param el the element
082 * @param pos0 the start position
083 * @param pos1 the end position
084 * @param axis the major axis
085 */
086 Zone(Element el, Position pos0, Position pos1, int axis)
087 {
088 super(el, axis);
089 p0 = pos0;
090 p1 = pos1;
091 }
092
093 /**
094 * Returns the start offset of the zone.
095 *
096 * @return the start offset of the zone
097 */
098 public int getStartOffset()
099 {
100 return p0.getOffset();
101 }
102
103 /**
104 * Returns the end offset of the zone.
105 *
106 * @return the end offset of the zone
107 */
108 public int getEndOffset()
109 {
110 return p1.getOffset();
111 }
112 }
113
114 /**
115 * The maximumZoneSize.
116 */
117 private int maximumZoneSize;
118
119 /**
120 * The maximum number of loaded zones.
121 */
122 private int maxZonesLoaded;
123
124 /**
125 * A queue of loaded zones. When the number of loaded zones exceeds the
126 * maximum number of zones, the oldest zone(s) get unloaded.
127 */
128 private LinkedList loadedZones;
129
130 /**
131 * Creates a new ZoneView for the specified element and axis.
132 *
133 * @param element the element for which to create a ZoneView
134 * @param axis the major layout axis for the box
135 */
136 public ZoneView(Element element, int axis)
137 {
138 super(element, axis);
139 maximumZoneSize = 8192;
140 maxZonesLoaded = 3;
141 loadedZones = new LinkedList();
142 }
143
144 /**
145 * Sets the maximum zone size. Note that zones might still become larger
146 * then the size specified when a singe child view is larger for itself,
147 * because zones are formed on child view boundaries.
148 *
149 * @param size the maximum zone size to set
150 *
151 * @see #getMaximumZoneSize()
152 */
153 public void setMaximumZoneSize(int size)
154 {
155 maximumZoneSize = size;
156 }
157
158 /**
159 * Returns the maximum zone size. Note that zones might still become larger
160 * then the size specified when a singe child view is larger for itself,
161 * because zones are formed on child view boundaries.
162 *
163 * @return the maximum zone size
164 *
165 * @see #setMaximumZoneSize(int)
166 */
167 public int getMaximumZoneSize()
168 {
169 return maximumZoneSize;
170 }
171
172 /**
173 * Sets the maximum number of zones that are allowed to be loaded at the
174 * same time. If the new number of allowed zones is smaller then the
175 * previous settings, this unloads all zones the aren't allowed to be
176 * loaded anymore.
177 *
178 * @param num the number of zones allowed to be loaded at the same time
179 *
180 * @throws IllegalArgumentException if <code>num <= 0</code>
181 *
182 * @see #getMaxZonesLoaded()
183 */
184 public void setMaxZonesLoaded(int num)
185 {
186 if (num < 1)
187 throw new IllegalArgumentException("Illegal number of zones");
188 maxZonesLoaded = num;
189 unloadOldestZones();
190 }
191
192 /**
193 * Returns the number of zones that are allowed to be loaded.
194 *
195 * @return the number of zones that are allowed to be loaded
196 *
197 * @see #setMaxZonesLoaded(int)
198 */
199 public int getMaxZonesLoaded()
200 {
201 return maxZonesLoaded;
202 }
203
204 /**
205 * Gets called after a zone has been loaded. This unloads the oldest zone(s)
206 * when the maximum number of zones is reached.
207 *
208 * @param zone the zone that has been loaded
209 */
210 protected void zoneWasLoaded(View zone)
211 {
212 loadedZones.addLast(zone);
213 unloadOldestZones();
214 }
215
216 /**
217 * This unloads the specified zone. This is implemented to simply remove
218 * all child views from that zone.
219 *
220 * @param zone the zone to be unloaded
221 */
222 protected void unloadZone(View zone)
223 {
224 zone.removeAll();
225 }
226
227 /**
228 * Returns <code>true</code> when the specified zone is loaded,
229 * <code>false</code> otherwise. The default implementation checks if
230 * the zone view has child elements.
231 *
232 * @param zone the zone view to check
233 *
234 * @return <code>true</code> when the specified zone is loaded,
235 * <code>false</code> otherwise
236 */
237 protected boolean isZoneLoaded(View zone)
238 {
239 return zone.getViewCount() > 0;
240 }
241
242 /**
243 * Creates a zone for the specified range. Subclasses can override this
244 * to provide a custom implementation for the zones.
245 *
246 * @param p0 the start of the range
247 * @param p1 the end of the range
248 *
249 * @return the zone
250 */
251 protected View createZone(int p0, int p1)
252 {
253 Document doc = getDocument();
254 Position pos0 = null;
255 Position pos1 = null;
256 try
257 {
258 pos0 = doc.createPosition(p0);
259 pos1 = doc.createPosition(p1);
260 }
261 catch (BadLocationException ex)
262 {
263 assert false : "Must not happen";
264 }
265 Zone zone = new Zone(getElement(), pos0, pos1, getAxis());
266 return zone;
267 }
268
269 // --------------------------------------------------------------------------
270 // CompositeView methods.
271 // --------------------------------------------------------------------------
272
273 /**
274 * Overridden to not load all the child views. This methods creates
275 * initial zones without actually loading them.
276 *
277 * @param vf not used
278 */
279 protected void loadChildren(ViewFactory vf)
280 {
281 int p0 = getStartOffset();
282 int p1 = getEndOffset();
283 append(createZone(p0, p1));
284 checkZoneAt(p0);
285 }
286
287 /**
288 * Returns the index of the child view at the document position
289 * <code>pos</code>.
290 *
291 * This overrides the CompositeView implementation because the ZoneView does
292 * not provide a one to one mapping from Elements to Views.
293 *
294 * @param pos the document position
295 *
296 * @return the index of the child view at the document position
297 * <code>pos</code>
298 */
299 protected int getViewIndexAtPosition(int pos)
300 {
301 int index = -1;
302 boolean found = false;
303 if (pos >= getStartOffset() && pos <= getEndOffset())
304 {
305 int upper = getViewCount() - 1;
306 int lower = 0;
307 index = (upper - lower) / 2 + lower;
308 int bias = 0;
309 do
310 {
311 View child = getView(index);
312 int childStart = child.getStartOffset();
313 int childEnd = child.getEndOffset();
314 if (pos >= childStart && pos < childEnd)
315 found = true;
316 else if (pos < childStart)
317 {
318 upper = index;
319 bias = -1;
320 }
321 else if (pos >= childEnd)
322 {
323 lower = index;
324 bias = 1;
325 }
326 if (! found)
327 {
328 int newIndex = (upper - lower) / 2 + lower;
329 if (newIndex == index)
330 index = newIndex + bias;
331 else
332 index = newIndex;
333 }
334 } while (upper != lower && ! found);
335 }
336 // If no child view actually covers the specified offset, reset index to
337 // -1.
338 if (! found)
339 index = -1;
340 return index;
341 }
342
343 // --------------------------------------------------------------------------
344 // View methods.
345 // --------------------------------------------------------------------------
346
347 public void insertUpdate(DocumentEvent e, Shape a, ViewFactory vf)
348 {
349 // TODO: Implement this.
350 }
351
352 public void removeUpdate(DocumentEvent e, Shape a, ViewFactory vf)
353 {
354 // TODO: Implement this.
355 }
356
357 protected boolean updateChildren(DocumentEvent.ElementChange ec,
358 DocumentEvent e, ViewFactory vf)
359 {
360 // TODO: Implement this.
361 return false;
362 }
363
364 // --------------------------------------------------------------------------
365 // Internal helper methods.
366 // --------------------------------------------------------------------------
367
368 /**
369 * A helper method to unload the oldest zones when there are more loaded
370 * zones then allowed.
371 */
372 private void unloadOldestZones()
373 {
374 int maxZones = getMaxZonesLoaded();
375 while (loadedZones.size() > maxZones)
376 {
377 View zone = (View) loadedZones.removeFirst();
378 unloadZone(zone);
379 }
380 }
381
382 /**
383 * Checks if the zone view at position <code>pos</code> should be split
384 * (its size is greater than maximumZoneSize) and tries to split it.
385 *
386 * @param pos the document position to check
387 */
388 private void checkZoneAt(int pos)
389 {
390 int viewIndex = getViewIndexAtPosition(pos); //, Position.Bias.Forward);
391 View view = getView(viewIndex);
392 int p0 = view.getStartOffset();
393 int p1 = view.getEndOffset();
394 if (p1 - p0 > maximumZoneSize)
395 splitZone(viewIndex, p0, p1);
396 }
397
398 /**
399 * Tries to break the view at the specified index and inside the specified
400 * range into pieces that are acceptable with respect to the maximum zone
401 * size.
402 *
403 * @param index the index of the view to split
404 * @param p0 the start offset
405 * @param p1 the end offset
406 */
407 private void splitZone(int index, int p0, int p1)
408 {
409 ArrayList newZones = new ArrayList();
410 int p = p0;
411 do
412 {
413 p0 = p;
414 p = Math.min(getPreferredZoneEnd(p0), p1);
415 newZones.add(createZone(p0, p));
416 } while (p < p1);
417 View[] newViews = new View[newZones.size()];
418 newViews = (View[]) newZones.toArray(newViews);
419 replace(index, 1, newViews);
420 }
421
422 /**
423 * Calculates the positions at which a zone split is performed. This
424 * tries to create zones sized close to half the maximum zone size.
425 *
426 * @param start the start offset
427 *
428 * @return the preferred end offset
429 */
430 private int getPreferredZoneEnd(int start)
431 {
432 Element el = getElement();
433 int index = el.getElementIndex(start + (maximumZoneSize / 2));
434 Element child = el.getElement(index);
435 int p0 = child.getStartOffset();
436 int p1 = child.getEndOffset();
437 int end = p1;
438 if (p0 - start > maximumZoneSize && p0 > start)
439 end = p0;
440 return end;
441 }
442 }