001: /*
002: * Sun Public License Notice
003: *
004: * The contents of this file are subject to the Sun Public License
005: * Version 1.0 (the "License"). You may not use this file except in
006: * compliance with the License. A copy of the License is available at
007: * http://www.sun.com/
008: *
009: * The Original Code is NetBeans. The Initial Developer of the Original
010: * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
011: * Microsystems, Inc. All Rights Reserved.
012: */
013:
014: package org.netbeans.editor;
015:
016: import java.util.Enumeration;
017: import java.util.Hashtable;
018:
019: import javax.swing.event.DocumentEvent;
020: import javax.swing.text.AbstractDocument;
021: import javax.swing.text.Element;
022: import javax.swing.undo.AbstractUndoableEdit;
023: import javax.swing.undo.CannotRedoException;
024: import javax.swing.undo.CannotUndoException;
025: import javax.swing.undo.UndoableEdit;
026:
027: /**
028: * Document implementation
029: *
030: * @author Miloslav Metelka
031: * @version 1.00
032: */
033:
034: public class BaseDocumentEvent extends
035: AbstractDocument.DefaultDocumentEvent {
036:
037: private DocOp.ModifyUndoEdit modifyUndoEdit;
038:
039: /**
040: * Previous event in the chain of the events that were connected together to
041: * be undone/redone at once.
042: */
043: private BaseDocumentEvent previous;
044:
045: private boolean inUndo;
046:
047: private boolean inRedo;
048:
049: /**
050: * Unfortunately the undo() and redo() methods call writeLock() which the
051: * protected final method in AbstractDocument. If somebody calls runAtomic()
052: * or runAtomicAsUser() and the exception is thrown in the body of the
053: * executed runnables, the document automatically undoes the changes.
054: * Unfortunately the undo() in AbstractDocument has the writeLock() call
055: * hardcoded which throws IllegalStateException() in this situation.
056: * Therefore the BaseDocumentEvent cannot call the super.undo() and has to
057: * reimplement the functionality of all the parents. The extWriteLock() and
058: * extWriteUnlock() are used instead.
059: */
060:
061: private boolean hasBeenDone2;
062:
063: private boolean alive2;
064:
065: private boolean inProgress2;
066:
067: private Hashtable changeLookup2;
068:
069: static final long serialVersionUID = -7624299835780414963L;
070:
071: /**
072: * Construct document event instance.
073: *
074: * @param offset
075: * position in the document where the insert/remove/change
076: * occured
077: * @param length
078: * number of the characters affected by the event
079: * @param type
080: * type of the event - INSERT/REMOVE/CHANGE
081: */
082: public BaseDocumentEvent(BaseDocument doc, int offset, int length,
083: DocumentEvent.EventType type) {
084: ((AbstractDocument) doc).super (offset, length, type);
085:
086: hasBeenDone2 = true;
087: alive2 = true;
088: inProgress2 = true;
089: }
090:
091: protected UndoableEdit findEdit(Class editClass) {
092: for (int i = edits.size() - 1; i >= 0; i--) {
093: Object edit = edits.get(i);
094: if (editClass.isInstance(edit)) {
095: return (UndoableEdit) edit;
096: }
097: }
098: return null;
099: }
100:
101: private DocOp.ModifyUndoEdit getModifyUndoEdit() {
102: if (getType() == DocumentEvent.EventType.CHANGE) {
103: throw new IllegalStateException(
104: "Cannot be called for CHANGE events."); // NOI18N
105: }
106:
107: if (modifyUndoEdit == null) {
108: modifyUndoEdit = (DocOp.ModifyUndoEdit) findEdit(DocOp.ModifyUndoEdit.class);
109: }
110: return modifyUndoEdit;
111: }
112:
113: /**
114: * Gets the characters that were inserted/removed or null for change event.
115: * Characters must be used only in readonly mode as the character array is
116: * shared by all listeners and also by modification event itself.
117: */
118: public char[] getChars() {
119: return (getModifyUndoEdit() != null) ? getModifyUndoEdit()
120: .getChars() : null;
121: }
122:
123: /**
124: * Get the text that was inserted/removed or null for change event.
125: */
126: public String getText() {
127: return (getModifyUndoEdit() != null) ? getModifyUndoEdit()
128: .getText() : null;
129: }
130:
131: /** Get the line at which the insert/remove occured */
132: public int getLine() {
133: return (getModifyUndoEdit() != null) ? getModifyUndoEdit()
134: .getLine() : 0;
135: }
136:
137: /**
138: * Get the count of '\n' (line-feeds) contained in the inserted/removed
139: * text.
140: */
141: public int getLFCount() {
142: return (getModifyUndoEdit() != null) ? getModifyUndoEdit()
143: .getLFCount() : 0;
144: }
145:
146: /**
147: * Get the offset at which the updating of the syntax stopped so there are
148: * no more changes in the tokens after this point.
149: */
150: public int getSyntaxUpdateOffset() {
151: return (getModifyUndoEdit() != null) ? getModifyUndoEdit()
152: .getSyntaxUpdateOffset() : 0;
153: }
154:
155: public String getDrawLayerName() {
156: if (getType() != DocumentEvent.EventType.CHANGE) {
157: throw new IllegalStateException(
158: "Can be called for CHANGE events only."); // NOI18N
159: }
160:
161: DrawLayerChange dlc = (DrawLayerChange) findEdit(DrawLayerChange.class);
162:
163: return (dlc != null) ? dlc.getDrawLayerName() : null;
164: }
165:
166: public int getDrawLayerVisibility() {
167: if (getType() != DocumentEvent.EventType.CHANGE) {
168: throw new IllegalStateException(
169: "Can be called for CHANGE events only."); // NOI18N
170: }
171:
172: DrawLayerChange dlc = (DrawLayerChange) findEdit(DrawLayerChange.class);
173:
174: return (dlc != null) ? dlc.getDrawLayerVisibility() : -1;
175: }
176:
177: /** Whether this event is being fired because it's being undone. */
178: public boolean isInUndo() {
179: return inUndo;
180: }
181:
182: /** Whether this event is being fired because it's being redone. */
183: public boolean isInRedo() {
184: return inRedo;
185: }
186:
187: public void undo() throws CannotUndoException {
188: BaseDocument doc = (BaseDocument) getDocument();
189:
190: inUndo = true;
191:
192: // Super of undo()
193: doc.extWriteLock(); // call this extWriteLock() instead of writeLock()
194: try {
195: if (!canUndo()) {
196: throw new CannotUndoException();
197: }
198: hasBeenDone2 = false;
199:
200: doc.lastModifyUndoEdit = null; // #8692 check last modify undo edit
201:
202: int i = edits.size();
203: while (i-- > 0) {
204: UndoableEdit e = (UndoableEdit) edits.elementAt(i);
205: e.undo();
206: }
207:
208: // fire a DocumentEvent to notify the view(s)
209: if (getType() == DocumentEvent.EventType.REMOVE) {
210: doc.fireInsertUpdate(this );
211: } else if (getType() == DocumentEvent.EventType.INSERT) {
212: doc.fireRemoveUpdate(this );
213: } else {
214: doc.fireChangedUpdate(this );
215: }
216: } finally {
217: doc.extWriteUnlock(); // call this extWriteUnlock() instead of
218: // writeUnlock()
219: }
220: // End super of undo()
221:
222: if (previous != null) {
223: previous.undo();
224: }
225:
226: inUndo = false;
227: }
228:
229: public void redo() throws CannotRedoException {
230: inRedo = true;
231: if (previous != null) {
232: previous.redo();
233: }
234:
235: BaseDocument doc = (BaseDocument) getDocument();
236:
237: // Super of redo()
238: doc.extWriteLock(); // call this extWriteLock() instead of writeLock()
239: try {
240:
241: if (!canRedo()) {
242: throw new CannotRedoException();
243: }
244: hasBeenDone2 = true;
245:
246: Enumeration cursor = edits.elements();
247: while (cursor.hasMoreElements()) {
248: ((UndoableEdit) cursor.nextElement()).redo();
249: }
250:
251: // fire a DocumentEvent to notify the view(s)
252: if (getType() == DocumentEvent.EventType.INSERT) {
253: doc.fireInsertUpdate(this );
254: } else if (getType() == DocumentEvent.EventType.REMOVE) {
255: doc.fireRemoveUpdate(this );
256: } else {
257: doc.fireChangedUpdate(this );
258: }
259: } finally {
260: doc.extWriteUnlock(); // call this extWriteUnlock() instead of
261: // writeUnlock()
262: }
263: // End super of redo()
264:
265: inRedo = false;
266: }
267:
268: public boolean addEdit(UndoableEdit anEdit) {
269: // Super of addEdit()
270:
271: // if the number of changes gets too great, start using
272: // a hashtable for to locate the change for a given element.
273: if ((changeLookup2 == null) && (edits.size() > 10)) {
274: changeLookup2 = new Hashtable();
275: int n = edits.size();
276: for (int i = 0; i < n; i++) {
277: Object o = edits.elementAt(i);
278: if (o instanceof DocumentEvent.ElementChange) {
279: DocumentEvent.ElementChange ec = (DocumentEvent.ElementChange) o;
280: changeLookup2.put(ec.getElement(), ec);
281: }
282: }
283: }
284:
285: // if we have a hashtable... add the entry if it's
286: // an ElementChange.
287: if ((changeLookup2 != null)
288: && (anEdit instanceof DocumentEvent.ElementChange)) {
289: DocumentEvent.ElementChange ec = (DocumentEvent.ElementChange) anEdit;
290: changeLookup2.put(ec.getElement(), ec);
291: }
292:
293: if (!inProgress2) {
294: return false;
295:
296: } else {
297: UndoableEdit last = lastEdit();
298:
299: // If this is the first subedit received, just add it.
300: // Otherwise, give the last one a chance to absorb the new
301: // one. If it won't, give the new one a chance to absorb
302: // the last one.
303:
304: if (last == null) {
305: edits.addElement(anEdit);
306: } else if (!last.addEdit(anEdit)) {
307: if (anEdit.replaceEdit(last)) {
308: edits.removeElementAt(edits.size() - 1);
309: }
310: edits.addElement(anEdit);
311: }
312:
313: return true;
314: }
315: // End super of addEdit()
316: }
317:
318: private boolean isLastModifyUndoEdit() {
319: if (getType() == DocumentEvent.EventType.CHANGE) {
320: return true; // OK in this case
321: }
322:
323: BaseDocument doc = (BaseDocument) getDocument();
324: doc.extWriteLock(); // lock to sync if ongoing doc change
325: try {
326: // #8692 check last modify undo edit
327: if (doc.lastModifyUndoEdit == null) {
328: return true; // OK in this case
329: }
330:
331: DocOp.ModifyUndoEdit undoEdit = getModifyUndoEdit();
332: return (undoEdit == doc.lastModifyUndoEdit);
333: } finally {
334: doc.extWriteUnlock();
335: }
336: }
337:
338: public boolean canUndo() {
339: // Super of canUndo
340: return !inProgress2 && alive2 && hasBeenDone2
341: // End super of canUndo
342: && isLastModifyUndoEdit();
343: }
344:
345: /**
346: * Returns false if isInProgress or if super does.
347: *
348: * @see #isInProgress
349: */
350: public boolean canRedo() {
351: // Super of canRedo
352: return !inProgress2 && alive2 && !hasBeenDone2;
353: // End super of canRedo
354: }
355:
356: public boolean isInProgress() {
357: // Super of isInProgress()
358: return inProgress2;
359: // End super of isInProgress()
360: }
361:
362: public String getUndoPresentationName() {
363: return "";
364: }
365:
366: public String getRedoPresentationName() {
367: return "";
368: }
369:
370: protected final BaseDocumentEvent getPrevious() {
371: return previous;
372: }
373:
374: /**
375: * Returns true if this event can be merged by the previous one (given as
376: * parameter) in the undo-manager queue.
377: */
378: public boolean canMerge(BaseDocumentEvent evt) {
379: if (getType() == DocumentEvent.EventType.INSERT) { // last was insert
380: if (evt.getType() == DocumentEvent.EventType.INSERT) { // adding
381: // insert to
382: // insert
383: String text = getText();
384: String evtText = evt.getText();
385: if ((getLength() == 1 || (getLength() > 1 && Analyzer
386: .isSpace(text)))
387: && (evt.getLength() == 1 || (evt.getLength() > 1 && Analyzer
388: .isSpace(evtText)))
389: && (evt.getOffset() + evt.getLength() == getOffset()) // this
390: // follows
391: // the
392: // previous
393: ) {
394: BaseDocument doc = (BaseDocument) getDocument();
395: boolean this Word = doc.isIdentifierPart(text
396: .charAt(0));
397: boolean lastWord = doc.isIdentifierPart(evtText
398: .charAt(0));
399: if (this Word && lastWord) { // add word char to word char(s)
400: return true;
401: }
402: boolean this White = doc
403: .isWhitespace(text.charAt(0));
404: boolean lastWhite = doc.isWhitespace(evtText
405: .charAt(0));
406: if ((lastWhite && this White)
407: || (!lastWhite && !lastWord && !this White && !this Word)) {
408: return true;
409: }
410: }
411: } else { // adding remove to insert
412: }
413: } else { // last was remove
414: if (evt.getType() == DocumentEvent.EventType.INSERT) { // adding
415: // insert to
416: // remove
417: } else { // adding remove to remove
418: }
419: }
420: return false;
421: }
422:
423: /**
424: * Try to determine whether this event can replace the old one. This is used
425: * to batch the one-letter modifications into larger parts (words) and
426: * undoing/redoing them at once. This method returns true whether
427: */
428: public boolean replaceEdit(UndoableEdit anEdit) {
429: BaseDocument doc = (BaseDocument) getDocument();
430: if (anEdit instanceof BaseDocumentEvent) {
431: BaseDocumentEvent evt = (BaseDocumentEvent) anEdit;
432:
433: if (!doc.undoMergeReset && canMerge(evt)) {
434: previous = evt;
435: return true;
436: }
437: }
438: doc.undoMergeReset = false;
439: return false;
440: }
441:
442: public void die() {
443: // Super of die()
444: int size = edits.size();
445: for (int i = size - 1; i >= 0; i--) {
446: UndoableEdit e = (UndoableEdit) edits.elementAt(i);
447: e.die();
448: }
449:
450: alive2 = false;
451: // End super of die()
452:
453: if (previous != null) {
454: previous.die();
455: previous = null;
456: }
457: }
458:
459: public void end() {
460: // Super of end()
461: inProgress2 = false;
462: // End super of end()
463: }
464:
465: public DocumentEvent.ElementChange getChange(Element elem) {
466: // Super of getChange()
467: if (changeLookup2 != null) {
468: return (DocumentEvent.ElementChange) changeLookup2
469: .get(elem);
470: }
471: int n = edits.size();
472: for (int i = 0; i < n; i++) {
473: Object o = edits.elementAt(i);
474: if (o instanceof DocumentEvent.ElementChange) {
475: DocumentEvent.ElementChange c = (DocumentEvent.ElementChange) o;
476: if (c.getElement() == elem) {
477: return c;
478: }
479: }
480: }
481: return null;
482: // End super of getChange()
483: }
484:
485: public String toString() {
486: return System.identityHashCode(this )
487: + " "
488: + super .toString()
489: + ", type="
490: + getType()
491: + ((getType() != DocumentEvent.EventType.CHANGE) ? ("text='"
492: + getText() + "'")
493: : "");
494: }
495:
496: /** Edit describing the change of the document draw-layers */
497: static class DrawLayerChange extends AbstractUndoableEdit {
498:
499: String drawLayerName;
500:
501: int drawLayerVisibility;
502:
503: DrawLayerChange(String drawLayerName, int drawLayerVisibility) {
504: this .drawLayerName = drawLayerName;
505: this .drawLayerVisibility = drawLayerVisibility;
506: }
507:
508: public String getDrawLayerName() {
509: return drawLayerName;
510: }
511:
512: public int getDrawLayerVisibility() {
513: return drawLayerVisibility;
514: }
515:
516: }
517:
518: }
|