package org.geogebra.common.kernel; import java.util.LinkedList; import java.util.ListIterator; import org.geogebra.common.main.App; /** * Undo manager common to Desktop and Web */ public abstract class UndoManager { /** * maximum capacity of undo info list: you can undo MAX_CAPACITY - 1 steps */ private static final int MAX_CAPACITY = 100; /** * Interface for application state * */ protected interface AppState { /** deletes this application state (i.e. deletes file) */ void delete(); } /** application */ public App app; /** construction */ protected Construction construction; /** list of undo steps */ protected LinkedList<AppState> undoInfoList; /** invariant: iterator.previous() is current state */ public ListIterator<AppState> iterator; /** * @param cons * construction */ public UndoManager(Construction cons) { construction = cons; app = cons.getApplication(); undoInfoList = new LinkedList<AppState>(); } /** * Processes XML * * @param string * XML string * @throws Exception * on trouble with parsing or running commands */ public abstract void processXML(String string) throws Exception; /** * Loads previous construction state from undo info list. */ public synchronized void undo() { if (undoPossible()) { iterator.previous(); loadUndoInfo(iterator.previous()); iterator.next(); updateUndoActions(); } } /** * Loads next construction state from undo info list. */ public synchronized void redo() { if (redoPossible()) { loadUndoInfo(iterator.next()); updateUndoActions(); } } /** * Update undo/redo buttons in GUI */ protected void updateUndoActions() { app.updateActions(); } /** * Get current undo info for later comparisons * * @return Object (the file of last undo) */ final public synchronized AppState getCurrentUndoInfo() { AppState ret = iterator.previous(); iterator.next(); return ret; } /** * Store undo info */ public void storeUndoInfo() { storeUndoInfo(false); } /** * Reloads construction state at current position of undo list (this is * needed for "cancel" actions). */ final public synchronized void restoreCurrentUndoInfo() { app.getSelectionManager().storeSelectedGeosNames(); if (iterator != null) { loadUndoInfo(iterator.previous()); iterator.next(); updateUndoActions(); } app.getSelectionManager().recallSelectedGeosNames(app.getKernel()); } /** * Clears undo info list and adds current state to the undo info list. */ public synchronized void initUndoInfo() { clearUndoInfo(); storeUndoInfo(); } /** * Returns whether undo operation is possible or not. * * @return whether undo operation is possible or not. */ public boolean undoPossible() { if (!app.isUndoActive()) { return false; } return iterator.nextIndex() > 1; } /** * Returns whether redo operation is possible or not. * * @return whether redo operation is possible or not. */ public boolean redoPossible() { if (!app.isUndoActive()) { return false; } return iterator.hasNext(); } /** * Stores undo info after pasting or adding new objects */ public abstract void storeUndoInfoAfterPasteOrAdd(); /** * @param currentUndoXML * construction XML * @param refresh * whether to reload afterwards */ public abstract void storeUndoInfo(StringBuilder currentUndoXML, boolean refresh); /** * Stores undo info * * @param refresh * true to restore current */ final public void storeUndoInfo(final boolean refresh) { storeUndoInfo(construction.getCurrentUndoXML(true), refresh); storeUndoInfoNeededForProperties = false; } /** * Loads undo info * * @param state * stored state */ protected abstract void loadUndoInfo(AppState state); /** * Clears all undo information */ public synchronized void clearUndoInfo() { undoInfoList.clear(); iterator = undoInfoList.listIterator(); } /** * Removes all stored states newer than current or too old */ public void pruneStateList() { // remove everything after the insert position until end of // list AppState appState = null; while (iterator.hasNext()) { appState = iterator.next(); iterator.remove(); appState.delete(); } // delete first if too many in list if (undoInfoList.size() > MAX_CAPACITY) { // use iterator to delete to avoid // ConcurrentModificationException // go to beginning of list while (iterator.hasPrevious()) { appState = iterator.previous(); } iterator.remove(); appState.delete(); while (iterator.hasNext()) { iterator.next(); } } } private boolean storeUndoInfoNeededForProperties = false; public void setPropertiesOccured() { storeUndoInfoNeededForProperties = true; } public void storeUndoInfoForProperties(boolean isUndoActive) { if (isUndoActive) { if (storeUndoInfoNeededForProperties) { storeUndoInfo(); } } storeUndoInfoNeededForProperties = false; } }