/*
GeoGebra - Dynamic Mathematics for Everyone
http://www.geogebra.org
This file is part of GeoGebra.
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation.
*/
package org.geogebra.desktop.kernel;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.AccessController;
import java.security.PrivilegedAction;
import javax.swing.DefaultListSelectionModel;
import org.geogebra.common.jre.io.MyXMLioJre;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.UndoManager;
import org.geogebra.common.main.App;
import org.geogebra.common.plugin.Event;
import org.geogebra.common.plugin.EventType;
import org.geogebra.common.util.debug.Log;
import org.geogebra.desktop.cas.view.CASViewD;
import org.geogebra.desktop.io.MyXMLioD;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* UndoManager handles undo information for a Construction. It uses an undo info
* list with construction snapshots in temporary files.
*
* @author Markus Hohenwarter
*/
public class UndoManagerD extends UndoManager {
/**
* Desktop version of ap stat: wrapper for file
*
*/
protected static class AppStateDesktop implements AppState {
private File f;
/**
* Wrap file into app state
*
* @param f
* file
*/
AppStateDesktop(File f) {
this.f = f;
}
/**
* Unwrap the file
*
* @return file
*/
public File getFile() {
return f;
}
@Override
@SuppressFBWarnings({ "RV_RETURN_VALUE_IGNORED_BAD_PRACTICE",
"don't need to check return value" })
public void delete() {
f.delete();
}
}
private static final String TEMP_FILE_PREFIX = "GeoGebraUndoInfo";
/**
* Creates a new UndowManager for the given Construction.
*
* @param cons
* construction
*/
public UndoManagerD(Construction cons) {
super(cons);
}
/**
* Adds construction state to undo info list
*/
@Override
public void storeUndoInfoAfterPasteOrAdd() {
// this can cause a java.lang.OutOfMemoryError for very large
// constructions
final StringBuilder currentUndoXML = construction
.getCurrentUndoXML(true);
// force create event dispatcher before we go to thread
Thread undoSaverThread = new Thread() {
@Override
public void run() {
doStoreUndoInfo(currentUndoXML);
app.getCopyPaste().pastePutDownCallback(app);
}
};
undoSaverThread.start();
}
/**
* Adds construction state to undo info list.
*/
@Override
public void storeUndoInfo(final StringBuilder currentUndoXML,
final boolean refresh) {
// force create event dispatcher before we go to thread
app.getEventDispatcher();
Thread undoSaverThread = new Thread() {
@Override
public void run() {
doStoreUndoInfo(currentUndoXML);
if (refresh) {
restoreCurrentUndoInfo();
}
}
};
undoSaverThread.start();
}
/**
* Adds construction state to undo info list.
*
* @param undoXML
* string builder with construction XML
*/
synchronized void doStoreUndoInfo(final StringBuilder undoXML) {
// avoid security problems calling from JavaScript ie setUndoPoint()
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
// perform the security-sensitive operation here
// save to file
File undoInfo = createTempFile(undoXML);
// insert undo info
AppState appStateToAdd = new AppStateDesktop(undoInfo);
iterator.add(appStateToAdd);
pruneStateList();
app.getEventDispatcher().dispatchEvent(
new Event(EventType.STOREUNDO, null));
} catch (Exception e) {
Log.debug("storeUndoInfo: " + e.toString());
e.printStackTrace();
} catch (java.lang.OutOfMemoryError err) {
Log.debug("UndoManager.storeUndoInfo: " + err.toString());
err.printStackTrace();
}
return null;
}
});
updateUndoActions();
}
/**
* Creates a temporary file containing the zipped undoXML.
*
* @param undoXML
* XML string
* @return temporary file
* @throws IOException
* on file creation problem
*/
synchronized static File createTempFile(StringBuilder undoXML)
throws IOException {
// create temp file
File tempFile = File.createTempFile(TEMP_FILE_PREFIX, ".ggb");
// Remove when program ends
tempFile.deleteOnExit();
// create file
FileOutputStream fos = new FileOutputStream(tempFile);
MyXMLioJre.writeZipped(fos, undoXML);
fos.close();
return tempFile;
}
/**
* restore info at position pos of undo list
*/
@Override
final protected synchronized void loadUndoInfo(final AppState info) {
InputStream is = null;
try {
// load from file
File tempFile = ((AppStateDesktop) info).getFile();
is = new FileInputStream(tempFile);
// make sure objects are displayed in the correct View
app.setActiveView(App.VIEW_EUCLIDIAN);
// needed for GGB-517
// keep information form listSelectionModel
CASViewD casView = null;
DefaultListSelectionModel listSelModel = null;
if (app.getGuiManager() != null && app.getGuiManager().hasCasView()
&& app.getView(App.VIEW_CAS) instanceof CASViewD) {
casView = (CASViewD) app.getView(App.VIEW_CAS);
}
if (casView != null && casView.getListSelModel() != null && casView
.getListSelModel() instanceof DefaultListSelectionModel) {
listSelModel = (DefaultListSelectionModel) casView
.getListSelModel();
}
int anchorIndex = 0;
int leadIndex = 0;
int maxIndex = 0;
int minIndex = 0;
boolean changed = false;
if (listSelModel != null) {
anchorIndex = listSelModel.getAnchorSelectionIndex();
leadIndex = listSelModel.getLeadSelectionIndex();
maxIndex = listSelModel.getMaxSelectionIndex();
minIndex = listSelModel.getMinSelectionIndex();
changed = true;
}
// load undo info
app.getScriptManager().disableListeners();
((MyXMLioD) construction.getXMLio()).readZipFromMemory(is);
if (changed) {
listSelModel.setAnchorSelectionIndex(anchorIndex);
listSelModel.setLeadSelectionIndex(leadIndex);
listSelModel.setSelectionInterval(minIndex, maxIndex);
}
app.getScriptManager().enableListeners();
} catch (Exception e) {
Log.error("setUndoInfo: " + e.toString());
e.printStackTrace();
restoreCurrentUndoInfo();
} catch (java.lang.OutOfMemoryError err) {
Log.error("UndoManager.loadUndoInfo: " + err.toString());
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
Log.error("setUndoInfo: " + e.toString());
e.printStackTrace();
}
}
}
/**
* Processes xml string. Note: this will change the construction.
*/
@Override
public synchronized void processXML(String strXML) throws Exception {
construction.setFileLoading(true);
construction.setCasCellUpdate(true);
((MyXMLioD) construction.getXMLio()).processXMLString(strXML, true,
false, true, true);
construction.setFileLoading(false);
construction.setCasCellUpdate(false);
}
}