Frames | No Frames |
1: /* ZoneView.java -- An effective BoxView subclass 2: Copyright (C) 2006 Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 6: GNU Classpath is free software; you can redistribute it and/or modify 7: it under the terms of the GNU General Public License as published by 8: the Free Software Foundation; either version 2, or (at your option) 9: any later version. 10: 11: GNU Classpath is distributed in the hope that it will be useful, but 12: WITHOUT ANY WARRANTY; without even the implied warranty of 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14: General Public License for more details. 15: 16: You should have received a copy of the GNU General Public License 17: along with GNU Classpath; see the file COPYING. If not, write to the 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 21: Linking this library statically or dynamically with other modules is 22: making a combined work based on this library. Thus, the terms and 23: conditions of the GNU General Public License cover the whole 24: combination. 25: 26: As a special exception, the copyright holders of this library give you 27: permission to link this library with independent modules to produce an 28: executable, regardless of the license terms of these independent 29: modules, and to copy and distribute the resulting executable under 30: terms of your choice, provided that you also meet, for each linked 31: independent module, the terms and conditions of the license of that 32: module. An independent module is a module which is not derived from 33: or based on this library. If you modify this library, you may extend 34: this exception to your version of the library, but you are not 35: obligated to do so. If you do not wish to do so, delete this 36: exception statement from your version. */ 37: 38: 39: package javax.swing.text; 40: 41: import java.awt.Shape; 42: import java.util.ArrayList; 43: import java.util.LinkedList; 44: 45: import javax.swing.event.DocumentEvent; 46: 47: /** 48: * A View implementation that delays loading of sub views until they are 49: * needed for display or internal transformations. This can be used for 50: * editors that need to handle large documents more effectivly than the 51: * standard {@link BoxView}. 52: * 53: * @author Roman Kennke (kennke@aicas.com) 54: * 55: * @since 1.3 56: */ 57: public class ZoneView 58: extends BoxView 59: { 60: 61: /** 62: * The default zone view implementation. The specs suggest that this is 63: * a subclass of AsyncBoxView, so do we. 64: */ 65: static class Zone 66: extends AsyncBoxView 67: { 68: /** 69: * The start position for this zone. 70: */ 71: private Position p0; 72: 73: /** 74: * The end position for this zone. 75: */ 76: private Position p1; 77: 78: /** 79: * Creates a new Zone for the specified element, start and end positions. 80: * 81: * @param el the element 82: * @param pos0 the start position 83: * @param pos1 the end position 84: * @param axis the major axis 85: */ 86: Zone(Element el, Position pos0, Position pos1, int axis) 87: { 88: super(el, axis); 89: p0 = pos0; 90: p1 = pos1; 91: } 92: 93: /** 94: * Returns the start offset of the zone. 95: * 96: * @return the start offset of the zone 97: */ 98: public int getStartOffset() 99: { 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: }