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 */ Zone(Element el, Position pos0, Position pos1, int axis)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 */ getStartOffset()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 */ getEndOffset()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 */ ZoneView(Element element, int axis)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 */ setMaximumZoneSize(int size)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 */ getMaximumZoneSize()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 */ setMaxZonesLoaded(int num)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 */ getMaxZonesLoaded()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 */ zoneWasLoaded(View zone)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 */ unloadZone(View zone)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 */ isZoneLoaded(View zone)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 */ createZone(int p0, int p1)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 */ loadChildren(ViewFactory vf)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 */ getViewIndexAtPosition(int pos)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 insertUpdate(DocumentEvent e, Shape a, ViewFactory vf)347 public void insertUpdate(DocumentEvent e, Shape a, ViewFactory vf) 348 { 349 // TODO: Implement this. 350 } 351 removeUpdate(DocumentEvent e, Shape a, ViewFactory vf)352 public void removeUpdate(DocumentEvent e, Shape a, ViewFactory vf) 353 { 354 // TODO: Implement this. 355 } 356 updateChildren(DocumentEvent.ElementChange ec, DocumentEvent e, ViewFactory vf)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 */ unloadOldestZones()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 */ checkZoneAt(int pos)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 */ splitZone(int index, int p0, int p1)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 */ getPreferredZoneEnd(int start)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 } 443