001: /*
002: * KillRing.java - Stores deleted text
003: * :tabSize=8:indentSize=8:noTabs=false:
004: * :folding=explicit:collapseFolds=1:
005: *
006: * Copyright (C) 2003, 2005 Slava Pestov
007: *
008: * This program is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU General Public License
010: * as published by the Free Software Foundation; either version 2
011: * of the License, or any later version.
012: *
013: * This program is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016: * GNU General Public License for more details.
017: *
018: * You should have received a copy of the GNU General Public License
019: * along with this program; if not, write to the Free Software
020: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
021: */
022:
023: package org.gjt.sp.jedit.buffer;
024:
025: import javax.swing.event.ListDataListener;
026: import java.util.List;
027:
028: import org.gjt.sp.jedit.gui.MutableListModel;
029:
030: /**
031: * The kill ring retains deleted text. This class is a singleton -- only one
032: * kill ring is used for all of jEdit. Nothing prevents plugins from making their
033: * own kill rings for whatever reason, though.
034: */
035: public class KillRing implements MutableListModel {
036: //{{{ getInstance() method
037: public static KillRing getInstance() {
038: return killRing;
039: } //}}}
040:
041: //{{{ setInstance() method
042: public static void setInstance(KillRing killRing) {
043: KillRing.killRing = killRing;
044: } //}}}
045:
046: //{{{ propertiesChanged() method
047: public void propertiesChanged(int historySize) {
048: int newSize = Math.max(1, historySize);
049: if (ring == null)
050: ring = new UndoManager.Remove[newSize];
051: else if (newSize != ring.length) {
052: UndoManager.Remove[] newRing = new UndoManager.Remove[newSize];
053: int newCount = Math.min(getSize(), newSize);
054: for (int i = 0; i < newCount; i++) {
055: newRing[i] = (UndoManager.Remove) getElementAt(i);
056: }
057: ring = newRing;
058: count = newCount;
059: wrap = false;
060: }
061:
062: if (count == ring.length) {
063: count = 0;
064: wrap = true;
065: }
066: } //}}}
067:
068: public void load() {
069: }
070:
071: public void save() {
072: }
073:
074: //{{{ reset() method
075: /**
076: * This method is made to be used by implementation of load()
077: * method to initialize (or reset) the killring by a loaded
078: * sequence of objects.
079: *
080: * Each element is converted to an element of the killring as
081: * followings:
082: * - If it is a String, it is converted as if it is a result of
083: * getElementAt(n).toString().
084: * - Otherwise, it is converted as if it is a Object which was
085: * obtained by getElementAt(n).
086: *
087: * @since jEdit 4.3pre12
088: */
089: protected void reset(List source) {
090: UndoManager.Remove[] newRing = new UndoManager.Remove[source
091: .size()];
092: int i = 0;
093: for (Object x : source) {
094: UndoManager.Remove element;
095: if (x instanceof String) {
096: element = new UndoManager.Remove(null, 0, 0, (String) x);
097: } else {
098: element = (UndoManager.Remove) x;
099: }
100: newRing[i++] = element;
101: }
102: ring = newRing;
103: count = 0;
104: wrap = true;
105: } //}}}
106:
107: //{{{ MutableListModel implementation
108: public void addListDataListener(ListDataListener listener) {
109: }
110:
111: public void removeListDataListener(ListDataListener listener) {
112: }
113:
114: //{{{ getElementAt() method
115: public Object getElementAt(int index) {
116: return ring[virtualToPhysicalIndex(index)];
117: } //}}}
118:
119: //{{{ getSize() method
120: public int getSize() {
121: if (wrap)
122: return ring.length;
123: else
124: return count;
125: } //}}}
126:
127: //{{{ removeElement() method
128: public boolean removeElement(Object value) {
129: for (int i = 0; i < getSize(); i++) {
130: if (ring[i].equals(value)) {
131: remove(i);
132: return true;
133: }
134: }
135: return false;
136: } //}}}
137:
138: //{{{ insertElementAt() method
139: public void insertElementAt(Object value, int index) {
140: /* This is not terribly efficient, but this method is only
141: called by the 'Paste Deleted' dialog where the performance
142: is not exactly vital */
143: remove(index);
144: add((UndoManager.Remove) value);
145: } //}}}
146:
147: //}}}
148:
149: //{{{ Package-private members
150:
151: //{{{ changed() method
152: void changed(UndoManager.Remove rem) {
153: if (rem.inKillRing) {
154: // compare existing entries' hashcode with this
155: int length = (wrap ? ring.length : count);
156: int kill = -1;
157:
158: for (int i = 0; i < length; i++) {
159: if (ring[i] != rem && ring[i].hashcode == rem.hashcode
160: && ring[i].str.equals(rem.str)) {
161: // we don't want duplicate
162: // entries in the kill ring
163: kill = i;
164: break;
165: }
166: }
167:
168: if (kill != -1)
169: remove(kill);
170: } else
171: add(rem);
172: } //}}}
173:
174: //{{{ add() method
175: void add(UndoManager.Remove rem) {
176: // compare existing entries' hashcode with this
177: int length = (wrap ? ring.length : count);
178: for (int i = 0; i < length; i++) {
179: if (ring[i].hashcode == rem.hashcode) {
180: // strings might be equal!
181: if (ring[i].str.equals(rem.str)) {
182: // we don't want duplicate entries
183: // in the kill ring
184: return;
185: }
186: }
187: }
188:
189: // no duplicates, check for all-whitespace string
190: boolean allWhitespace = true;
191: for (int i = 0; i < rem.str.length(); i++) {
192: if (!Character.isWhitespace(rem.str.charAt(i))) {
193: allWhitespace = false;
194: break;
195: }
196: }
197:
198: if (allWhitespace)
199: return;
200:
201: rem.inKillRing = true;
202:
203: if (ring[count] != null)
204: ring[count].inKillRing = false;
205:
206: ring[count] = rem;
207: if (++count >= ring.length) {
208: wrap = true;
209: count = 0;
210: }
211: } //}}}
212:
213: //{{{ remove() method
214: void remove(int i) {
215: if (wrap) {
216: UndoManager.Remove[] newRing = new UndoManager.Remove[ring.length];
217: int newCount = 0;
218: for (int j = 0; j < ring.length; j++) {
219: int index = virtualToPhysicalIndex(j);
220:
221: if (i == index) {
222: ring[index].inKillRing = false;
223: continue;
224: }
225:
226: newRing[newCount++] = ring[index];
227: }
228: ring = newRing;
229: count = newCount;
230: wrap = false;
231: } else {
232: System.arraycopy(ring, i + 1, ring, i, count - i - 1);
233: count--;
234: }
235: } //}}}
236:
237: //}}}
238:
239: //{{{ Private members
240: private UndoManager.Remove[] ring;
241: private int count;
242: private boolean wrap;
243: private static KillRing killRing = new KillRing();
244:
245: //{{{ virtualToPhysicalIndex() method
246: /**
247: * Since the kill ring has a wrap-around representation, we need to
248: * convert user-visible indices to actual indices in the array.
249: */
250: private int virtualToPhysicalIndex(int index) {
251: if (wrap) {
252: if (index < count)
253: return count - index - 1;
254: else
255: return count + ring.length - index - 1;
256: } else
257: return count - index - 1;
258: } //}}}
259:
260: //}}}
261: }
|