1 /* 2 * Copyright (c) 1998, 2008, Oracle and/or its affiliates. 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package javax.swing.text; 26 27 import java.util.Vector; 28 import java.io.IOException; 29 import java.io.ObjectInputStream; 30 import java.io.Serializable; 31 import javax.swing.undo.AbstractUndoableEdit; 32 import javax.swing.undo.CannotRedoException; 33 import javax.swing.undo.CannotUndoException; 34 import javax.swing.undo.UndoableEdit; 35 import javax.swing.SwingUtilities; 36 import java.lang.ref.WeakReference; 37 import java.lang.ref.ReferenceQueue; 38 39 /** 40 * An implementation of the AbstractDocument.Content interface 41 * implemented using a gapped buffer similar to that used by emacs. 42 * The underlying storage is a array of unicode characters with 43 * a gap somewhere. The gap is moved to the location of changes 44 * to take advantage of common behavior where most changes are 45 * in the same location. Changes that occur at a gap boundary are 46 * generally cheap and moving the gap is generally cheaper than 47 * moving the array contents directly to accommodate the change. 48 * <p> 49 * The positions tracking change are also generally cheap to 50 * maintain. The Position implementations (marks) store the array 51 * index and can easily calculate the sequential position from 52 * the current gap location. Changes only require update to the 53 * the marks between the old and new gap boundaries when the gap 54 * is moved, so generally updating the marks is pretty cheap. 55 * The marks are stored sorted so they can be located quickly 56 * with a binary search. This increases the cost of adding a 57 * mark, and decreases the cost of keeping the mark updated. 58 * 59 * @author Timothy Prinzing 60 */ 61 public class GapContent extends GapVector implements AbstractDocument.Content, Serializable { 62 63 /** 64 * Creates a new GapContent object. Initial size defaults to 10. 65 */ 66 public GapContent() { 67 this(10); 68 } 69 70 /** 71 * Creates a new GapContent object, with the initial 72 * size specified. The initial size will not be allowed 73 * to go below 2, to give room for the implied break and 74 * the gap. 75 * 76 * @param initialLength the initial size 77 */ 78 public GapContent(int initialLength) { 79 super(Math.max(initialLength,2)); 80 char[] implied = new char[1]; 81 implied[0] = '\n'; 82 replace(0, 0, implied, implied.length); 83 84 marks = new MarkVector(); 85 search = new MarkData(0); 86 queue = new ReferenceQueue<StickyPosition>(); 87 } 88 89 /** 90 * Allocate an array to store items of the type 91 * appropriate (which is determined by the subclass). 92 */ 93 protected Object allocateArray(int len) { 94 return new char[len]; 95 } 96 97 /** 98 * Get the length of the allocated array. 99 */ 100 protected int getArrayLength() { 101 char[] carray = (char[]) getArray(); 102 return carray.length; 103 } 104 105 // --- AbstractDocument.Content methods ------------------------- 106 107 /** 108 * Returns the length of the content. 109 * 110 * @return the length >= 1 111 * @see AbstractDocument.Content#length 112 */ 113 public int length() { 114 int len = getArrayLength() - (getGapEnd() - getGapStart()); 115 return len; 116 } 117 118 /** 119 * Inserts a string into the content. 120 * 121 * @param where the starting position >= 0, < length() 122 * @param str the non-null string to insert 123 * @return an UndoableEdit object for undoing 124 * @exception BadLocationException if the specified position is invalid 125 * @see AbstractDocument.Content#insertString 126 */ 127 public UndoableEdit insertString(int where, String str) throws BadLocationException { 128 if (where > length() || where < 0) { 129 throw new BadLocationException("Invalid insert", length()); 130 } 131 char[] chars = str.toCharArray(); 132 replace(where, 0, chars, chars.length); 133 return new InsertUndo(where, str.length()); 134 } 135 136 /** 137 * Removes part of the content. 138 * 139 * @param where the starting position >= 0, where + nitems < length() 140 * @param nitems the number of characters to remove >= 0 141 * @return an UndoableEdit object for undoing 142 * @exception BadLocationException if the specified position is invalid 143 * @see AbstractDocument.Content#remove 144 */ 145 public UndoableEdit remove(int where, int nitems) throws BadLocationException { 146 if (where + nitems >= length()) { 147 throw new BadLocationException("Invalid remove", length() + 1); 148 } 149 String removedString = getString(where, nitems); 150 UndoableEdit edit = new RemoveUndo(where, removedString); 151 replace(where, nitems, empty, 0); 152 return edit; 153 154 } 155 156 /** 157 * Retrieves a portion of the content. 158 * 159 * @param where the starting position >= 0 160 * @param len the length to retrieve >= 0 161 * @return a string representing the content 162 * @exception BadLocationException if the specified position is invalid 163 * @see AbstractDocument.Content#getString 164 */ 165 public String getString(int where, int len) throws BadLocationException { 166 Segment s = new Segment(); 167 getChars(where, len, s); 168 return new String(s.array, s.offset, s.count); 169 } 170 171 /** 172 * Retrieves a portion of the content. If the desired content spans 173 * the gap, we copy the content. If the desired content does not 174 * span the gap, the actual store is returned to avoid the copy since 175 * it is contiguous. 176 * 177 * @param where the starting position >= 0, where + len <= length() 178 * @param len the number of characters to retrieve >= 0 179 * @param chars the Segment object to return the characters in 180 * @exception BadLocationException if the specified position is invalid 181 * @see AbstractDocument.Content#getChars 182 */ 183 public void getChars(int where, int len, Segment chars) throws BadLocationException { 184 int end = where + len; 185 if (where < 0 || end < 0) { 186 throw new BadLocationException("Invalid location", -1); 187 } 188 if (end > length() || where > length()) { 189 throw new BadLocationException("Invalid location", length() + 1); 190 } 191 int g0 = getGapStart(); 192 int g1 = getGapEnd(); 193 char[] array = (char[]) getArray(); 194 if ((where + len) <= g0) { 195 // below gap 196 chars.array = array; 197 chars.offset = where; 198 } else if (where >= g0) { 199 // above gap 200 chars.array = array; 201 chars.offset = g1 + where - g0; 202 } else { 203 // spans the gap 204 int before = g0 - where; 205 if (chars.isPartialReturn()) { 206 // partial return allowed, return amount before the gap 207 chars.array = array; 208 chars.offset = where; 209 chars.count = before; 210 return; 211 } 212 // partial return not allowed, must copy 213 chars.array = new char[len]; 214 chars.offset = 0; 215 System.arraycopy(array, where, chars.array, 0, before); 216 System.arraycopy(array, g1, chars.array, before, len - before); 217 } 218 chars.count = len; 219 } 220 221 /** 222 * Creates a position within the content that will 223 * track change as the content is mutated. 224 * 225 * @param offset the offset to track >= 0 226 * @return the position 227 * @exception BadLocationException if the specified position is invalid 228 */ 229 public Position createPosition(int offset) throws BadLocationException { 230 while ( queue.poll() != null ) { 231 unusedMarks++; 232 } 233 if (unusedMarks > Math.max(5, (marks.size() / 10))) { 234 removeUnusedMarks(); 235 } 236 int g0 = getGapStart(); 237 int g1 = getGapEnd(); 238 int index = (offset < g0) ? offset : offset + (g1 - g0); 239 search.index = index; 240 int sortIndex = findSortIndex(search); 241 MarkData m; 242 StickyPosition position; 243 if (sortIndex < marks.size() 244 && (m = marks.elementAt(sortIndex)).index == index 245 && (position = m.getPosition()) != null) { 246 //position references the correct StickyPostition 247 } else { 248 position = new StickyPosition(); 249 m = new MarkData(index,position,queue); 250 position.setMark(m); 251 marks.insertElementAt(m, sortIndex); 252 } 253 254 return position; 255 } 256 257 /** 258 * Holds the data for a mark... separately from 259 * the real mark so that the real mark (Position 260 * that the caller of createPosition holds) can be 261 * collected if there are no more references to 262 * it. The update table holds only a reference 263 * to this data. 264 */ 265 final class MarkData extends WeakReference<StickyPosition> { 266 267 MarkData(int index) { 268 super(null); 269 this.index = index; 270 } 271 MarkData(int index, StickyPosition position, ReferenceQueue<? super StickyPosition> queue) { 272 super(position, queue); 273 this.index = index; 274 } 275 276 /** 277 * Fetch the location in the contiguous sequence 278 * being modeled. The index in the gap array 279 * is held by the mark, so it is adjusted according 280 * to it's relationship to the gap. 281 */ 282 public final int getOffset() { 283 int g0 = getGapStart(); 284 int g1 = getGapEnd(); 285 int offs = (index < g0) ? index : index - (g1 - g0); 286 return Math.max(offs, 0); 287 } 288 289 StickyPosition getPosition() { 290 return get(); 291 } 292 int index; 293 } 294 295 final class StickyPosition implements Position { 296 297 StickyPosition() { 298 } 299 300 void setMark(MarkData mark) { 301 this.mark = mark; 302 } 303 304 public final int getOffset() { 305 return mark.getOffset(); 306 } 307 308 public String toString() { 309 return Integer.toString(getOffset()); 310 } 311 312 MarkData mark; 313 } 314 315 // --- variables -------------------------------------- 316 317 private static final char[] empty = new char[0]; 318 private transient MarkVector marks; 319 320 /** 321 * Record used for searching for the place to 322 * start updating mark indexs when the gap 323 * boundaries are moved. 324 */ 325 private transient MarkData search; 326 327 /** 328 * The number of unused mark entries 329 */ 330 private transient int unusedMarks = 0; 331 332 private transient ReferenceQueue<StickyPosition> queue; 333 334 final static int GROWTH_SIZE = 1024 * 512; 335 336 // --- gap management ------------------------------- 337 338 /** 339 * Make the gap bigger, moving any necessary data and updating 340 * the appropriate marks 341 */ 342 protected void shiftEnd(int newSize) { 343 int oldGapEnd = getGapEnd(); 344 345 super.shiftEnd(newSize); 346 347 // Adjust marks. 348 int dg = getGapEnd() - oldGapEnd; 349 int adjustIndex = findMarkAdjustIndex(oldGapEnd); 350 int n = marks.size(); 351 for (int i = adjustIndex; i < n; i++) { 352 MarkData mark = marks.elementAt(i); 353 mark.index += dg; 354 } 355 } 356 357 /** 358 * Overridden to make growth policy less agressive for large 359 * text amount. 360 */ 361 int getNewArraySize(int reqSize) { 362 if (reqSize < GROWTH_SIZE) { 363 return super.getNewArraySize(reqSize); 364 } else { 365 return reqSize + GROWTH_SIZE; 366 } 367 } 368 369 /** 370 * Move the start of the gap to a new location, 371 * without changing the size of the gap. This 372 * moves the data in the array and updates the 373 * marks accordingly. 374 */ 375 protected void shiftGap(int newGapStart) { 376 int oldGapStart = getGapStart(); 377 int dg = newGapStart - oldGapStart; 378 int oldGapEnd = getGapEnd(); 379 int newGapEnd = oldGapEnd + dg; 380 int gapSize = oldGapEnd - oldGapStart; 381 382 // shift gap in the character array 383 super.shiftGap(newGapStart); 384 385 // update the marks 386 if (dg > 0) { 387 // Move gap up, move data and marks down. 388 int adjustIndex = findMarkAdjustIndex(oldGapStart); 389 int n = marks.size(); 390 for (int i = adjustIndex; i < n; i++) { 391 MarkData mark = marks.elementAt(i); 392 if (mark.index >= newGapEnd) { 393 break; 394 } 395 mark.index -= gapSize; 396 } 397 } else if (dg < 0) { 398 // Move gap down, move data and marks up. 399 int adjustIndex = findMarkAdjustIndex(newGapStart); 400 int n = marks.size(); 401 for (int i = adjustIndex; i < n; i++) { 402 MarkData mark = marks.elementAt(i); 403 if (mark.index >= oldGapEnd) { 404 break; 405 } 406 mark.index += gapSize; 407 } 408 } 409 resetMarksAtZero(); 410 } 411 412 /** 413 * Resets all the marks that have an offset of 0 to have an index of 414 * zero as well. 415 */ 416 protected void resetMarksAtZero() { 417 if (marks != null && getGapStart() == 0) { 418 int g1 = getGapEnd(); 419 for (int counter = 0, maxCounter = marks.size(); 420 counter < maxCounter; counter++) { 421 MarkData mark = marks.elementAt(counter); 422 if (mark.index <= g1) { 423 mark.index = 0; 424 } 425 else { 426 break; 427 } 428 } 429 } 430 } 431 432 /** 433 * Adjust the gap end downward. This doesn't move 434 * any data, but it does update any marks affected 435 * by the boundary change. All marks from the old 436 * gap start down to the new gap start are squeezed 437 * to the end of the gap (their location has been 438 * removed). 439 */ 440 protected void shiftGapStartDown(int newGapStart) { 441 // Push aside all marks from oldGapStart down to newGapStart. 442 int adjustIndex = findMarkAdjustIndex(newGapStart); 443 int n = marks.size(); 444 int g0 = getGapStart(); 445 int g1 = getGapEnd(); 446 for (int i = adjustIndex; i < n; i++) { 447 MarkData mark = marks.elementAt(i); 448 if (mark.index > g0) { 449 // no more marks to adjust 450 break; 451 } 452 mark.index = g1; 453 } 454 455 // shift the gap in the character array 456 super.shiftGapStartDown(newGapStart); 457 458 resetMarksAtZero(); 459 } 460 461 /** 462 * Adjust the gap end upward. This doesn't move 463 * any data, but it does update any marks affected 464 * by the boundary change. All marks from the old 465 * gap end up to the new gap end are squeezed 466 * to the end of the gap (their location has been 467 * removed). 468 */ 469 protected void shiftGapEndUp(int newGapEnd) { 470 int adjustIndex = findMarkAdjustIndex(getGapEnd()); 471 int n = marks.size(); 472 for (int i = adjustIndex; i < n; i++) { 473 MarkData mark = marks.elementAt(i); 474 if (mark.index >= newGapEnd) { 475 break; 476 } 477 mark.index = newGapEnd; 478 } 479 480 // shift the gap in the character array 481 super.shiftGapEndUp(newGapEnd); 482 483 resetMarksAtZero(); 484 } 485 486 /** 487 * Compares two marks. 488 * 489 * @param o1 the first object 490 * @param o2 the second object 491 * @return < 0 if o1 < o2, 0 if the same, > 0 if o1 > o2 492 */ 493 final int compare(MarkData o1, MarkData o2) { 494 if (o1.index < o2.index) { 495 return -1; 496 } else if (o1.index > o2.index) { 497 return 1; 498 } else { 499 return 0; 500 } 501 } 502 503 /** 504 * Finds the index to start mark adjustments given 505 * some search index. 506 */ 507 final int findMarkAdjustIndex(int searchIndex) { 508 search.index = Math.max(searchIndex, 1); 509 int index = findSortIndex(search); 510 511 // return the first in the series 512 // (ie. there may be duplicates). 513 for (int i = index - 1; i >= 0; i--) { 514 MarkData d = marks.elementAt(i); 515 if (d.index != search.index) { 516 break; 517 } 518 index -= 1; 519 } 520 return index; 521 } 522 523 /** 524 * Finds the index of where to insert a new mark. 525 * 526 * @param o the mark to insert 527 * @return the index 528 */ 529 final int findSortIndex(MarkData o) { 530 int lower = 0; 531 int upper = marks.size() - 1; 532 int mid = 0; 533 534 if (upper == -1) { 535 return 0; 536 } 537 538 int cmp; 539 MarkData last = marks.elementAt(upper); 540 cmp = compare(o, last); 541 if (cmp > 0) 542 return upper + 1; 543 544 while (lower <= upper) { 545 mid = lower + ((upper - lower) / 2); 546 MarkData entry = marks.elementAt(mid); 547 cmp = compare(o, entry); 548 549 if (cmp == 0) { 550 // found a match 551 return mid; 552 } else if (cmp < 0) { 553 upper = mid - 1; 554 } else { 555 lower = mid + 1; 556 } 557 } 558 559 // didn't find it, but we indicate the index of where it would belong. 560 return (cmp < 0) ? mid : mid + 1; 561 } 562 563 /** 564 * Remove all unused marks out of the sorted collection 565 * of marks. 566 */ 567 final void removeUnusedMarks() { 568 int n = marks.size(); 569 MarkVector cleaned = new MarkVector(n); 570 for (int i = 0; i < n; i++) { 571 MarkData mark = marks.elementAt(i); 572 if (mark.get() != null) { 573 cleaned.addElement(mark); 574 } 575 } 576 marks = cleaned; 577 unusedMarks = 0; 578 } 579 580 581 static class MarkVector extends GapVector { 582 583 MarkVector() { 584 super(); 585 } 586 587 MarkVector(int size) { 588 super(size); 589 } 590 591 /** 592 * Allocate an array to store items of the type 593 * appropriate (which is determined by the subclass). 594 */ 595 protected Object allocateArray(int len) { 596 return new MarkData[len]; 597 } 598 599 /** 600 * Get the length of the allocated array 601 */ 602 protected int getArrayLength() { 603 MarkData[] marks = (MarkData[]) getArray(); 604 return marks.length; 605 } 606 607 /** 608 * Returns the number of marks currently held 609 */ 610 public int size() { 611 int len = getArrayLength() - (getGapEnd() - getGapStart()); 612 return len; 613 } 614 615 /** 616 * Inserts a mark into the vector 617 */ 618 public void insertElementAt(MarkData m, int index) { 619 oneMark[0] = m; 620 replace(index, 0, oneMark, 1); 621 } 622 623 /** 624 * Add a mark to the end 625 */ 626 public void addElement(MarkData m) { 627 insertElementAt(m, size()); 628 } 629 630 /** 631 * Fetches the mark at the given index 632 */ 633 public MarkData elementAt(int index) { 634 int g0 = getGapStart(); 635 int g1 = getGapEnd(); 636 MarkData[] array = (MarkData[]) getArray(); 637 if (index < g0) { 638 // below gap 639 return array[index]; 640 } else { 641 // above gap 642 index += g1 - g0; 643 return array[index]; 644 } 645 } 646 647 /** 648 * Replaces the elements in the specified range with the passed 649 * in objects. This will NOT adjust the gap. The passed in indices 650 * do not account for the gap, they are the same as would be used 651 * int <code>elementAt</code>. 652 */ 653 protected void replaceRange(int start, int end, Object[] marks) { 654 int g0 = getGapStart(); 655 int g1 = getGapEnd(); 656 int index = start; 657 int newIndex = 0; 658 Object[] array = (Object[]) getArray(); 659 if (start >= g0) { 660 // Completely passed gap 661 index += (g1 - g0); 662 end += (g1 - g0); 663 } 664 else if (end >= g0) { 665 // straddles gap 666 end += (g1 - g0); 667 while (index < g0) { 668 array[index++] = marks[newIndex++]; 669 } 670 index = g1; 671 } 672 else { 673 // below gap 674 while (index < end) { 675 array[index++] = marks[newIndex++]; 676 } 677 } 678 while (index < end) { 679 array[index++] = marks[newIndex++]; 680 } 681 } 682 683 MarkData[] oneMark = new MarkData[1]; 684 685 } 686 687 // --- serialization ------------------------------------- 688 689 private void readObject(ObjectInputStream s) 690 throws ClassNotFoundException, IOException { 691 s.defaultReadObject(); 692 marks = new MarkVector(); 693 search = new MarkData(0); 694 queue = new ReferenceQueue<StickyPosition>(); 695 } 696 697 698 // --- undo support -------------------------------------- 699 700 /** 701 * Returns a Vector containing instances of UndoPosRef for the 702 * Positions in the range 703 * <code>offset</code> to <code>offset</code> + <code>length</code>. 704 * If <code>v</code> is not null the matching Positions are placed in 705 * there. The vector with the resulting Positions are returned. 706 * 707 * @param v the Vector to use, with a new one created on null 708 * @param offset the starting offset >= 0 709 * @param length the length >= 0 710 * @return the set of instances 711 */ 712 protected Vector getPositionsInRange(Vector v, int offset, int length) { 713 int endOffset = offset + length; 714 int startIndex; 715 int endIndex; 716 int g0 = getGapStart(); 717 int g1 = getGapEnd(); 718 719 // Find the index of the marks. 720 if (offset < g0) { 721 if (offset == 0) { 722 // findMarkAdjustIndex start at 1! 723 startIndex = 0; 724 } 725 else { 726 startIndex = findMarkAdjustIndex(offset); 727 } 728 if (endOffset >= g0) { 729 endIndex = findMarkAdjustIndex(endOffset + (g1 - g0) + 1); 730 } 731 else { 732 endIndex = findMarkAdjustIndex(endOffset + 1); 733 } 734 } 735 else { 736 startIndex = findMarkAdjustIndex(offset + (g1 - g0)); 737 endIndex = findMarkAdjustIndex(endOffset + (g1 - g0) + 1); 738 } 739 740 Vector placeIn = (v == null) ? new Vector(Math.max(1, endIndex - 741 startIndex)) : v; 742 743 for (int counter = startIndex; counter < endIndex; counter++) { 744 placeIn.addElement(new UndoPosRef(marks.elementAt(counter))); 745 } 746 return placeIn; 747 } 748 749 /** 750 * Resets the location for all the UndoPosRef instances 751 * in <code>positions</code>. 752 * <p> 753 * This is meant for internal usage, and is generally not of interest 754 * to subclasses. 755 * 756 * @param positions the UndoPosRef instances to reset 757 */ 758 protected void updateUndoPositions(Vector positions, int offset, 759 int length) { 760 // Find the indexs of the end points. 761 int endOffset = offset + length; 762 int g1 = getGapEnd(); 763 int startIndex; 764 int endIndex = findMarkAdjustIndex(g1 + 1); 765 766 if (offset != 0) { 767 startIndex = findMarkAdjustIndex(g1); 768 } 769 else { 770 startIndex = 0; 771 } 772 773 // Reset the location of the refenences. 774 for(int counter = positions.size() - 1; counter >= 0; counter--) { 775 UndoPosRef ref = (UndoPosRef)positions.elementAt(counter); 776 ref.resetLocation(endOffset, g1); 777 } 778 // We have to resort the marks in the range startIndex to endIndex. 779 // We can take advantage of the fact that it will be in 780 // increasing order, accept there will be a bunch of MarkData's with 781 // the index g1 (or 0 if offset == 0) interspersed throughout. 782 if (startIndex < endIndex) { 783 Object[] sorted = new Object[endIndex - startIndex]; 784 int addIndex = 0; 785 int counter; 786 if (offset == 0) { 787 // If the offset is 0, the positions won't have incremented, 788 // have to do the reverse thing. 789 // Find the elements in startIndex whose index is 0 790 for (counter = startIndex; counter < endIndex; counter++) { 791 MarkData mark = marks.elementAt(counter); 792 if (mark.index == 0) { 793 sorted[addIndex++] = mark; 794 } 795 } 796 for (counter = startIndex; counter < endIndex; counter++) { 797 MarkData mark = marks.elementAt(counter); 798 if (mark.index != 0) { 799 sorted[addIndex++] = mark; 800 } 801 } 802 } 803 else { 804 for (counter = startIndex; counter < endIndex; counter++) { 805 MarkData mark = marks.elementAt(counter); 806 if (mark.index != g1) { 807 sorted[addIndex++] = mark; 808 } 809 } 810 for (counter = startIndex; counter < endIndex; counter++) { 811 MarkData mark = marks.elementAt(counter); 812 if (mark.index == g1) { 813 sorted[addIndex++] = mark; 814 } 815 } 816 } 817 // And replace 818 marks.replaceRange(startIndex, endIndex, sorted); 819 } 820 } 821 822 /** 823 * Used to hold a reference to a Mark that is being reset as the 824 * result of removing from the content. 825 */ 826 final class UndoPosRef { 827 UndoPosRef(MarkData rec) { 828 this.rec = rec; 829 this.undoLocation = rec.getOffset(); 830 } 831 832 /** 833 * Resets the location of the Position to the offset when the 834 * receiver was instantiated. 835 * 836 * @param endOffset end location of inserted string. 837 * @param g1 resulting end of gap. 838 */ 839 protected void resetLocation(int endOffset, int g1) { 840 if (undoLocation != endOffset) { 841 this.rec.index = undoLocation; 842 } 843 else { 844 this.rec.index = g1; 845 } 846 } 847 848 /** Previous Offset of rec. */ 849 protected int undoLocation; 850 /** Mark to reset offset. */ 851 protected MarkData rec; 852 } // End of GapContent.UndoPosRef 853 854 855 /** 856 * UnoableEdit created for inserts. 857 */ 858 class InsertUndo extends AbstractUndoableEdit { 859 protected InsertUndo(int offset, int length) { 860 super(); 861 this.offset = offset; 862 this.length = length; 863 } 864 865 public void undo() throws CannotUndoException { 866 super.undo(); 867 try { 868 // Get the Positions in the range being removed. 869 posRefs = getPositionsInRange(null, offset, length); 870 string = getString(offset, length); 871 remove(offset, length); 872 } catch (BadLocationException bl) { 873 throw new CannotUndoException(); 874 } 875 } 876 877 public void redo() throws CannotRedoException { 878 super.redo(); 879 try { 880 insertString(offset, string); 881 string = null; 882 // Update the Positions that were in the range removed. 883 if(posRefs != null) { 884 updateUndoPositions(posRefs, offset, length); 885 posRefs = null; 886 } 887 } catch (BadLocationException bl) { 888 throw new CannotRedoException(); 889 } 890 } 891 892 /** Where string was inserted. */ 893 protected int offset; 894 /** Length of string inserted. */ 895 protected int length; 896 /** The string that was inserted. This will only be valid after an 897 * undo. */ 898 protected String string; 899 /** An array of instances of UndoPosRef for the Positions in the 900 * range that was removed, valid after undo. */ 901 protected Vector posRefs; 902 } // GapContent.InsertUndo 903 904 905 /** 906 * UndoableEdit created for removes. 907 */ 908 class RemoveUndo extends AbstractUndoableEdit { 909 protected RemoveUndo(int offset, String string) { 910 super(); 911 this.offset = offset; 912 this.string = string; 913 this.length = string.length(); 914 posRefs = getPositionsInRange(null, offset, length); 915 } 916 917 public void undo() throws CannotUndoException { 918 super.undo(); 919 try { 920 insertString(offset, string); 921 // Update the Positions that were in the range removed. 922 if(posRefs != null) { 923 updateUndoPositions(posRefs, offset, length); 924 posRefs = null; 925 } 926 string = null; 927 } catch (BadLocationException bl) { 928 throw new CannotUndoException(); 929 } 930 } 931 932 public void redo() throws CannotRedoException { 933 super.redo(); 934 try { 935 string = getString(offset, length); 936 // Get the Positions in the range being removed. 937 posRefs = getPositionsInRange(null, offset, length); 938 remove(offset, length); 939 } catch (BadLocationException bl) { 940 throw new CannotRedoException(); 941 } 942 } 943 944 /** Where the string was removed from. */ 945 protected int offset; 946 /** Length of string removed. */ 947 protected int length; 948 /** The string that was removed. This is valid when redo is valid. */ 949 protected String string; 950 /** An array of instances of UndoPosRef for the Positions in the 951 * range that was removed, valid before undo. */ 952 protected Vector posRefs; 953 } // GapContent.RemoveUndo 954 }