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