/* ****************************************************************************** * * Copyright 2008-2010 Hans Dijkema * * JRichTextEditor is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * JRichTextEditor is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with JRichTextEditor. If not, see <http://www.gnu.org/licenses/>. * * ******************************************************************************/ package nl.dykema.jxmlnote.undo; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.Vector; import javax.swing.event.UndoableEditEvent; import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; import javax.swing.undo.UndoManager; import javax.swing.undo.UndoableEdit; import nl.dykema.jxmlnote.internationalization.DefaultXMLNoteTranslator; import nl.dykema.jxmlnote.internationalization.XMLNoteTranslator; import nl.dykema.jxmlnote.undo.MyStack.Operate; /** * XMLNoteUndoManager overrides the standard Swing UndoManager. It adds methods to make the undo manager * ignore the next undoable edits. Further, it adds a state updater interface, that is called after * important changes to the undo manager have occurred (the methods that apply will document this). * * * @author Hans Dijkema * */ public class XMLNoteUndoManager extends UndoManager { private static final long serialVersionUID = 1L; class LongUndoableEdit implements UndoableEdit { private UndoableEdit _current,_previous; public boolean addEdit(UndoableEdit anEdit) { return _current.addEdit(anEdit); } public boolean canRedo() { return _current.canRedo(); } public boolean canUndo() { return _current.canUndo(); } public void die() { _current.die(); if (_previous!=null) { _previous.die(); } } public String getPresentationName() { return _current.getPresentationName(); } public String getRedoPresentationName() { return _current.getRedoPresentationName(); } public String getUndoPresentationName() { return _current.getUndoPresentationName(); } public boolean isSignificant() { return _current.isSignificant(); } public void redo() throws CannotRedoException { if (_previous!=null) { _previous.redo(); } _current.redo(); } public boolean replaceEdit(UndoableEdit anEdit) { return _current.replaceEdit(anEdit); } public void undo() throws CannotUndoException { _current.undo(); if (_previous!=null) { _previous.undo(); } } public LongUndoableEdit(UndoableEdit e) { _current=e; _previous=null; } public LongUndoableEdit(UndoableEdit e,UndoableEdit prev) { _current=e; _previous=prev; } } class EndLongUndoableEdit extends LongUndoableEdit { public EndLongUndoableEdit(LongUndoableEdit e) { super(e._current,e._previous); } } private boolean _ignore=false; private boolean _longEdit=false; private LongUndoableEdit _le=null; ////////////////////////////////////////////////////////////////////////////////////////////////// // Updater interface ////////////////////////////////////////////////////////////////////////////////////////////////// /** * Stat Updater interface. * @author Hans Dijkema * */ public interface StateUpdater { public void update(XMLNoteUndoManager m); } private Set<StateUpdater> _updaters; boolean _inupdate=false; private boolean inupdate(boolean b) { boolean q=_inupdate; _inupdate=b; return q; } private void informUpdaters() { if (_inupdate) { return; } Iterator<StateUpdater> it=_updaters.iterator(); while(it.hasNext()) { it.next().update(this); } } /** * Add state updater that is called when something important occurs with the undo manager that should * be reflected in the state. * @param u */ public void addStateUpdater(StateUpdater u) { _updaters.add(u); } /** * Remove the previously added state updater. * @param u */ public void removeStateUpdater(StateUpdater u) { _updaters.remove(u); } ////////////////////////////////////////////////////////////////////////////////////////////////// // Reimplementation of UndoManager ////////////////////////////////////////////////////////////////////////////////////////////////// private XMLNoteTranslator _tr=new DefaultXMLNoteTranslator(); private MyStack<UndoableEdit> _undoStack=new MyStack<UndoableEdit>(); private MyStack<UndoableEdit> _redoStack=new MyStack<UndoableEdit>(); private int _limit=250; private void clearStack(MyStack<UndoableEdit> s) { while (!s.isEmpty()) { s.pop().die(); } } private void limitStack(MyStack<UndoableEdit> s) { if (s.size()>=_limit) { int toDrop=s.size()-_limit; s.dropUntilLimit(toDrop, new Operate<UndoableEdit>() { public void doIt(UndoableEdit e) { e.die(); } }); } } private void limitStacks() { limitStack(_undoStack); limitStack(_redoStack); } /** * Overrides <code>addEdit()</code> to implement ignore behaviour. * * @return */ public synchronized boolean addEdit(UndoableEdit anEdit) { if (_ignore) { return true; } else { clearStack(_redoStack); limitStacks(); boolean b=inupdate(true); if (_longEdit) { if (_undoStack.isEmpty()) { _undoStack.push(new LongUndoableEdit(anEdit)); } else if (_undoStack.peek() instanceof EndLongUndoableEdit) { _undoStack.push(new LongUndoableEdit(anEdit)); } else if (_undoStack.peek() instanceof LongUndoableEdit) { _undoStack.push(new LongUndoableEdit(anEdit,_undoStack.pop())); } else { _undoStack.push(new LongUndoableEdit(anEdit)); } } else { _undoStack.push(anEdit); } inupdate(b); informUpdaters(); return true; } } /** * Overrides <code>discardAllEdits()</code>. Calls the state updaters after it calls the super method. */ public synchronized void discardAllEdits() { boolean b=inupdate(true); clearStack(_redoStack); clearStack(_undoStack); inupdate(b); informUpdaters(); } /** * Overrides <code>undo()</code>. Calls the state updaters after it calls the super method. */ public synchronized void undo() { boolean b=inupdate(true); //super.undo(); if (!_undoStack.isEmpty()) { UndoableEdit e=_undoStack.pop(); e.undo(); _redoStack.push(e); } limitStacks(); inupdate(b); informUpdaters(); } /** * Overrides <code>redo()</code>. Calls the state updaters after it calls the super method. */ public synchronized void redo() { boolean b=inupdate(true); if (!_redoStack.isEmpty()) { UndoableEdit e=_redoStack.pop(); e.redo(); _undoStack.push(e); } limitStacks(); inupdate(b); informUpdaters(); } public synchronized boolean canRedo() { return !_redoStack.isEmpty() && _redoStack.peek().canRedo(); } public synchronized boolean canUndo() { return !_undoStack.isEmpty() && _undoStack.peek().canUndo(); } public synchronized boolean canUndoOrRedo() { return canRedo() || canUndo(); } public synchronized void end() { while(!_redoStack.isEmpty()) { _redoStack.pop().die(); } } public synchronized int getLimit() { return _limit; } public synchronized String getRedoPresentationName() { if (!_redoStack.isEmpty()) { return _redoStack.peek().getRedoPresentationName(); } else { return _tr._("Nothing to redo"); } } public synchronized String getUndoPresentationName() { if (_undoStack.isEmpty()) { return _undoStack.peek().getUndoPresentationName(); } else { return _tr._("Nothing to undo"); } } public synchronized String getUndoOrRedoPresentationName() { if (_undoStack.isEmpty()) { return getRedoPresentationName(); } else{ return getUndoPresentationName(); } } public synchronized void setLimit(int l) { _limit=l; } public String toString() { return "XMLNoteUndoManager:Limit="+Integer.toString(_limit)+";Redo="+_redoStack.toString()+";Undo="+_undoStack.toString(); } public void undoableEditHappened(UndoableEditEvent e) { addEdit(e.getEdit()); } public void undoOrRedo() { boolean b=inupdate(true); if (_undoStack.isEmpty()) { redo(); } else { undo(); } inupdate(b); informUpdaters(); } ////////////////////////////////////////////////////////////////////////////////////////////////// // Ignore edits for undo ////////////////////////////////////////////////////////////////////////////////////////////////// /** * If set to true, all addEdit() operations will be ignored until it is set to false. * The previous value is returned. * * @param i */ public boolean setIgnore(boolean i) { boolean ii=_ignore; _ignore=i; return ii; } /** * Returns true, if addEdit() will ignore addEdits. * * @return */ public boolean ignores() { return _ignore; } /** * Sets long edit mode. If b==true, subsequent addEdit() calls will be assembled in one. * When b==false, the combined edit will be committed using an actual internal addEdit() * that makes the changes. * * @param b * @return */ public boolean setLongEdit(boolean b) { boolean q=_longEdit; _longEdit=b; if (!_longEdit) { if (!_undoStack.isEmpty()) { if (_undoStack.peek() instanceof LongUndoableEdit) { if (_undoStack.peek() instanceof EndLongUndoableEdit) { // do nothing } else { LongUndoableEdit a=(LongUndoableEdit) _undoStack.pop(); EndLongUndoableEdit ea=new EndLongUndoableEdit(a); _undoStack.push(ea); } } } } return q; } /** * Constructs the XMLNoteUpdateManager. */ public XMLNoteUndoManager() { _updaters=new HashSet<StateUpdater>(); } } class MyStackException extends RuntimeException { public MyStackException(String msg) { super(msg); } } class MyStack<T> extends Vector<T> { public interface Operate<T> { public void doIt(T e); } private void checkEmpty() { if (isEmpty()) { throw new MyStackException("Empty Stack"); } } public T peek() { checkEmpty(); return super.get(super.size()-1); } public T pop() { T elem=peek(); super.remove(super.size()-1); return elem; } public void push(T e) { super.add(e); } public void dropUntilLimit(int limit,Operate<T> o) { int i; for(i=0;i<limit;i++) { o.doIt(super.get(i)); } super.removeRange(0, limit); } }