package bibliothek.help.control;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import bibliothek.help.model.Entry;
/**
* The <code>URManager</code> is used by a {@link LinkManager} to store
* the history of undo/redo-steps. Any client might use the methods of this
* <code>URManager</code> to <i>undo</i> or <i>redo</i> an action.
* @author Benjamin Sigg
*/
public class URManager {
/** the available steps */
private LinkedList<Step> stack = new LinkedList<Step>();
/**
* The {@link Step} that is currently selected (that would be undone
* if {@link #undo()} is used)
*/
private int current = -1;
/** whether this <code>URManager</code> is currently running an operation */
private boolean onChange = false;
/** the list of listeners to inform whenever the {@link #stack()} and {@link #current selection} changes */
private List<URListener> listeners = new ArrayList<URListener>();
/** the list of elements whose contents must be considered when storing or applying a {@link Step} */
private List<Undoable> undoables = new ArrayList<Undoable>();
/**
* Adds a new element whose content will be considered when storing
* or applying a {@link Step}.
* @param undoable the mutable element showing an {@link Entry}
*/
public void register( Undoable undoable ){
undoables.add( undoable );
}
/**
* Gets the index of the currently selected {@link Step}. That's the
* index of the <code>Step</code> that would be undone when {@link #undo()}
* is called.
* @return the current step or -1
*/
public int getCurrent(){
return current;
}
/**
* Gets the currently available <code>Step</code>s as an independent
* array.
* @return the stack of <code>Step</code>s
*/
public Step[] stack(){
return stack.toArray( new Step[ stack.size() ] );
}
/**
* Adds a listener to this manager, the listener will be informed whenever
* the {@link #stack() stack} or the {@link #getCurrent() selection} changes.
* @param listener the new observer
*/
public void addListener( URListener listener ){
listeners.add( listener );
}
/**
* Removes a listener that was previously added from this manager.
* @param listener the listener to remove
*/
public void removeListener( URListener listener ){
listeners.remove( listener );
}
/**
* Informs all registered {@link URListener}s that the content
* of this manager has changed.
*/
protected void fire(){
for( URListener listener : listeners )
listener.changed( this );
}
/**
* Informs this manager that the selected set of pages has
* changed. This manager will add a new {@link Step} to the {@link #stack()},
* and might delete some <code>Step</code>s when necessary.
* <br>Clients should not call this method unless they created
* the {@link URManager} for themselves.
* @param entry the new selection
*/
public void selected( Entry entry ) {
if( !onChange ){
int remove = stack.size() - current;
while( --remove > 0 )
stack.removeLast();
stack.addLast( new Step( entry ) );
while( stack.size() > 25 )
stack.removeFirst();
current = stack.size()-1;
fire();
}
}
/**
* Selects the content of the <code>index</code>'th {@link Step} to
* show.
* @param index the index of the newly selected <code>Step</code>
*/
public void moveTo( int index ){
if( index != current ){
current = index;
stack.get( current ).apply();
fire();
}
}
/**
* Tells whether the {@link #undo()}-action will have any effect or not.
* @return <code>true</code> if <i>undo</i> is possible
*/
public boolean isUndoable(){
return current > 0;
}
/**
* Selects the {@link Step} that is one below the {@link #getCurrent() current}
* <code>Step</code> in the {@link #stack()} and ensures that the
* content of this new <code>Step</code> is shown. Does nothing if there
* is no available <code>Step</code>.
*/
public void undo(){
if( isUndoable() ){
onChange = true;
current--;
stack.get( current ).apply();
onChange = false;
fire();
}
}
/**
* Tells whether the {@link #redo()}-action will have and effect or not.
* @return <code>true</code> if <i>redo</i> is possible
*/
public boolean isRedoable(){
return current+1 < stack.size();
}
/**
* Selects the {@link Step} that is one above the {@link #getCurrent() current}
* <code>Step</code> in the {@link #stack()} and ensures that the
* content of this new <code>Step</code> is shown. Does nothing if there
* is no available <code>Step</code>.
*/
public void redo(){
if( isRedoable() ){
onChange = true;
current++;
stack.get( current ).apply();
onChange = false;
fire();
}
}
/**
* A <code>Step</code> stores for every known {@link Undoable} which
* {@link Entry} it showed when the <code>Step</code> was created. These
* <code>Entries</code> can be set again using {@link #apply()}.
* @author Benjamin Sigg
*
*/
public class Step{
/** a small description of this <code>Step</code> */
private String title;
/** a map telling for each {@link Undoable} which {@link Entry} it showed */
private Map<Undoable, Entry> selection;
/**
* Creates a new <code>Step</code>
* @param entry the most important {@link Entry} of this <code>Step</code>,
* used to get a {@link #getTitle() small description}
*/
public Step( Entry entry ){
title = entry.getTitle();
selection = new HashMap<Undoable, Entry>();
for( Undoable undoable : undoables )
selection.put( undoable, undoable.getCurrent() );
}
/**
* A small description of this <code>Step</code>.
* @return the description
*/
public String getTitle(){
return title;
}
/**
* Ensures that every {@link Undoable} shows the same {@link Entry} as
* it showed when this <code>Step</code> was created.
*/
public void apply(){
for( Map.Entry<Undoable, Entry> entry : selection.entrySet() )
entry.getKey().setCurrent( entry.getValue() );
}
}
}