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