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