/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package edu.mit.csail.sdg.alloy4;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.SimpleAttributeSet;
import static java.lang.System.arraycopy;
/** Graphical syntax-highlighting StyledDocument with undo+redo support.
*
* <p><b>Thread Safety:</b> Can be called only by the AWT event thread
*/
final class OurSyntaxUndoableDocument extends OurSyntaxDocument {
/** This ensures the class can be serialized reliably. */
private static final long serialVersionUID = 0;
/** The maximum number of UNDO actions we want to keep. */
private static final int MAXUNDO = 100;
/** For each i = 0..now-1, insert[i] means whether the i-th operation was an insertion or not. */
private boolean insert[] = new boolean[MAXUNDO];
/** For each i = 0..now-1, text[i] means the i-th operation was an insertion/deletion of that text. */
private String text[] = new String[MAXUNDO];
/** For each i = 0..now-1, where[i] means the i-th operation was an insertion/deletion at that offset. */
private int where[] = new int[MAXUNDO];
/** The number of undoable operations currently remembered in insert[], text[], and where[]. */
private int now;
/** The number of undoable opeartions that are currently "undone". */
private int undone;
/** Caches a default AttributeSet (just so that we don't pass null as a AttributeSet. */
private final AttributeSet attr = new SimpleAttributeSet();
/** Constructor. */
public OurSyntaxUndoableDocument(String fontName, int fontSize) { super(fontName, fontSize); }
/** Clear the undo history. */
public void do_clearUndo() { now = undone = 0; }
/** Returns true if we can perform undo right now. */
public boolean do_canUndo() { return undone < now; }
/** Returns true if we can perform redo right now. */
public boolean do_canRedo() { return undone > 0 ; }
/** Perform undo then return where the new desired caret location should be (or return -1 if undo is not possible right now) */
public int do_undo() {
if (undone >= now) return -1; else undone++;
boolean insert = this.insert[now - undone];
String text = this.text[now - undone];
int where = this.where[now - undone];
try {
if (insert) { super.remove(where, text.length()); return where; }
else { super.insertString(where, text, attr); return where + text.length(); }
} catch(BadLocationException ex) { return -1; }
}
/** Perform redo then return where the new desired caret location should be (or return -1 if redo is not possible right now) */
public int do_redo() {
if (undone <= 0) return -1;
boolean insert = this.insert[now - undone];
String text = this.text[now - undone];
int where = this.where[now - undone];
undone--;
try {
if (insert) { super.insertString(where, text, attr); return where + text.length(); }
else { super.remove(where, text.length()); return where; }
} catch(BadLocationException ex) { return -1; }
}
/** This method is called by Swing to insert a String into this document. */
@Override public void insertString(int offset, String string, AttributeSet attr) throws BadLocationException {
if (string.length()==0) return; else if (string.indexOf('\r')>=0) string = Util.convertLineBreak(string); // we don't want '\r'
if (undone > 0) { now = now - undone; undone = 0; } // clear the REDO entries
super.insertString(offset, string, attr);
if (now > 0 && insert[now-1]) { // merge with last edit if possible
if (where[now-1] == offset - text[now-1].length()) { text[now - 1] += string; return; }
}
if (now >= MAXUNDO) {
arraycopy(insert, 1, insert, 0, MAXUNDO-1);
arraycopy(text, 1, text, 0, MAXUNDO-1);
arraycopy(where, 1, where, 0, MAXUNDO-1);
now--;
}
insert[now]=true; text[now]=string; where[now]=offset; now++;
}
/** This method is called by Swing to delete text from this document. */
@Override public void remove(int offset, int length) throws BadLocationException {
if (length==0) return;
if (undone > 0) { now = now - undone; undone = 0; } // clear the REDO entries
String string = toString().substring(offset, offset+length);
super.remove(offset, length);
if (now > 0 && !insert[now-1]) { // merge with last edit if possible
if (where[now-1] == offset) { text[now-1] += string; return; }
if (where[now-1] == offset+length) { where[now-1] = offset; text[now-1] = string + text[now-1]; return; }
}
if (now >= MAXUNDO) {
arraycopy(insert, 1, insert, 0, MAXUNDO-1);
arraycopy(text, 1, text, 0, MAXUNDO-1);
arraycopy(where, 1, where, 0, MAXUNDO-1);
now--;
}
insert[now]=false; text[now]=string; where[now]=offset; now++;
}
/** This method is called by Swing to replace text in this document. */
@Override public void replace(int offset, int length, String string, AttributeSet attrs) throws BadLocationException {
if (length > 0) this.remove(offset, length);
if (string != null && string.length() > 0) this.insertString(offset, string, this.attr);
}
/** Overriden to return the full text of the document.
* @return the entire text
*/
@Override public String toString() {
try { return getText(0, getLength()); } catch(BadLocationException ex) { return ""; }
}
}