/* * EuroCarbDB, a framework for carbohydrate bioinformatics * * Copyright (c) 2006-2009, Eurocarb project, or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * A copy of this license accompanies this distribution in the file LICENSE.txt. * * This program 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. * * Last commit: $Rev: 1210 $ by $Author: glycoslave $ on $Date:: 2009-06-12 #$ */ package org.eurocarbdb.application.glycanbuilder; import java.util.*; /** Simple implementation of a manager for undo/redo of a {@link BaseDocument} object. Every time the underlying document changes the complete state of the object is save using the {@link BaseDocument#toString} method. The previous state of the document is restored using the {@link BaseDocument#fromString(String)} method. The manage can store a limited number of previous document states. @author Alessio Ceroni (a.ceroni@imperial.ac.uk) */ public class GlycanUndoManager implements BaseDocument.DocumentChangeListener { private static final int MAXIMUM_NUMBER_OF_STATES = 20; // protected BaseDocument theDoc = null; protected boolean doing = false; protected int cur_state = -1; protected int no_actions = 0; protected boolean was_changed = false; protected LinkedList<String> states = new LinkedList<String>(); protected Vector<GlycanUndoRedoListener> listeners = new Vector<GlycanUndoRedoListener>(); // /** Default constructor @param _theDoc the document was state changes are observed by this undo manager */ public GlycanUndoManager(BaseDocument _theDoc) { theDoc = _theDoc; theDoc.addDocumentChangeListener(this); reset(); } /** Register a listener that is notified every time the underlying document's state is changed */ public void addUndoRedoListener(GlycanUndoRedoListener l) { if( l!=null ) listeners.add(l); } /** De-reegister one of the listeners that are notified every time the underlying document's state is changed */ public void removeUndoRedoListener(GlycanUndoRedoListener l) { if( l!=null ) listeners.remove(l); } /** Reset the state of the undo manager. Clear the all the stored information */ public void reset() { states.clear(); states.add(theDoc.toString()); cur_state = 0; no_actions = 0; was_changed = theDoc.hasChanged(); } /** Return <code>true</code> if the state of the underlying document has changed and can be restored */ public boolean canUndo() { return (cur_state>0); } /** Restore the state of the underlying document if possible */ public void undo() throws Exception { if( cur_state<=0 ) return; doing = true; cur_state--; no_actions--; theDoc.fill(states.get(cur_state),(no_actions==0 && !was_changed)); doing = false; fireUndoRedoAction(true); } /** Return <code>true</code> if the state of the underlying document has been restored to a previous state and the changes can be applied again */ public boolean canRedo() { return (cur_state<(states.size()-1)); } /** Apply the saved changes to the underlying document if it has been restored to a previous state */ public void redo() throws Exception { if( cur_state>=(states.size()-1) ) return; doing = true; cur_state++; no_actions++; theDoc.fill(states.get(cur_state),false); doing = false; fireUndoRedoAction(false); } /** Return <code>true</code> if the underlying document is changed from the previous stored state */ public boolean isChanged() { if( cur_state>=0 ) { String last_state_str = states.get(cur_state); String cur_state_str = theDoc.toString(); return !last_state_str.equals(cur_state_str); } return true; } // /** Notify all listeners that either an undo or redo action has been performed */ public void fireUndoRedoAction(boolean is_undo) { GlycanUndoRedoEvent e = new GlycanUndoRedoEvent(this,is_undo); for(Iterator<GlycanUndoRedoListener> i=listeners.iterator(); i.hasNext(); ) i.next().undoRedoHappened(e); } /** React to the initialization of the underlying document by clearing the saved states. This event can happens either after the document has been renewed or saved. @see #reset */ public void documentInit(BaseDocument.DocumentChangeEvent e) { if( e.getSource()==theDoc ) reset(); } /** Listen for changes in the underlying document. Store the current document state. */ public void documentChanged(BaseDocument.DocumentChangeEvent e) { if( e.getSource()==theDoc && !doing && isChanged() ) { // clear following actions while( cur_state<(states.size()-1) ) states.removeLast(); // add new action states.addLast(theDoc.toString()); // limit the size of the queue while( states.size()>MAXIMUM_NUMBER_OF_STATES ) states.removeFirst(); // update indices cur_state = states.size()-1; no_actions++; } } }