Source for javax.swing.text.ZoneView

   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 &lt;= 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: }