/*
* Copyright (C) 2011 Alasdair C. Hamilton
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package ketUI.chord;
import java.io.*;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import ket.*; //?
import ket.display.*;
import ket.display.box.Box;
import ket.treeDiff.TreeDiff;
import ket.treeDiff.Painter;
import ket.math.*;
import ketUI.*;
import ketUI.modes.Modes;
import ketUI.panel.AnimatedDisplay;
import ketUI.panel.KetPanel;
import ketUI.responder.*;
/**
* After a keypress is handled by KeyboardEventHandler, it is assembled into a
* ketUI.chord which is either processed here or delegated to a more specialised
* class.
*/
public class ChordEventHandler {
private static boolean ANIMATE_SAVED_IMAGES = true;
final Document document;
final MathCollection mathCollection;
final KeyboardEventHandler keyboardEventHandler;
/**
* Unique log file name to for program profiling.
*/
String uniqueName;
String imageName;
int imageNumber;
public ChordEventHandler(Document document, KeyboardEventHandler keyboardEventHandler) {
this.document = document;
this.mathCollection = getModes().getMathCollection();
this.keyboardEventHandler = keyboardEventHandler;
imageName = null;
imageNumber = 0;
}
/**
* Images can be recorded as the user words, and these are saved to the
* given filename (a png file).
*/
public void startSavingImages(String imageName) {
this.imageName = imageName;
}
public boolean imagesAreSaved() {
return this.imageName != null;
}
/**
* This stops saving images of the equations that are currently being
* modified.
*/
public void stopSavingImages() {
this.imageName = null;
}
/**
* This method processes a given ketUI.chord. If the ketUI.chord contains an
* error, or is incomplete, the method returns with the cord in the
* appropriate state. However, if the ketUI.chord is correctly executed then
* it be run 'count' times when a count has been specified (within
* ketUI.chord).
*/
public void processChord(Chord chord, Macros macros) {
int counts = chord.getCounts();
for (int count=0; count<counts; count++) {
Responder responder = getModes().getResponder();
responder.respondToChordEvent(chord, macros, keyboardEventHandler);
//- Ket.out.println("C: current' = " + mathCollection.getCursor().getCurrent()); // CURRENT -> '' HAPPENS BEFORE HERE
if (chord.isError()) {
keyboardEventHandler.error();
return;
} else if (!chord.isComplete()){
// Wait for the rest of the chord to be typed.
updateForAnimatedDisplay();
return;
}
if (chord.isLoopSkipped()) {
// Count is handled within responder.
break;
}
}
chordSuccess(chord, macros);
mathCollection.updateUndoStack();
keyboardEventHandler.awaitNewChord();
}
private void chordSuccess(Chord chord, Macros macros) {
logToFile(chord);
macros.appendIfRecording(chord);
// TODO: Change explicity boolean to subBranchEquals comparison with undo (this is already done, but not in its own public method).
// TODO: Add a suppress change flag within chord for undo, 'u'; redo, 'r'; macro record and play, 'q' and '@'.
// TODO: Remove the return type of all responders etc.
//- Ket.out.println(" *** up to date undo stack?"+mathCollection.undoStackIsUpToDate()+" *** ");
// Note: Undo and redo will always result in an up-to-date undo
// stack and hence will not be recorded here. Consider this
// further as recording undos is different to animating or
// saving undo steps.
boolean upToDate = mathCollection.undoStackIsUpToDate();
if (upToDate) {
keyboardEventHandler.awaitNewChord();
return;
}
updateForAnimatedDisplay(); // TODO: This should also be updated for undos and redos.
keyboardEventHandler.updateOldChord(); // How does undo affect this?
mathCollection.setCursorSelection();
if (imageName==null) {
return;
}
if (ANIMATE_SAVED_IMAGES) { // TODO: This should also be updated for undos and redos.
// Animate
// TODO: Split into separate classes: diff algorithm and image daemon.
Address oldCursor = mathCollection.getUndo().getAddress();
Ket.out.println("old cursor = " + oldCursor);
Argument before = mathCollection.getUndo().getLastRoot();
Argument after = mathCollection.getCursor().getCurrent();
if (before!=null) {
Painter painter = new Painter(before, after, imageName, imageNumber);
imageNumber += Painter.FRAME_NUMBER;
painter.start();
}
} else {
// Save a single frame.
Equation currentEquation = mathCollection.getCursor().getEquation();
saveAsImage(currentEquation);
}
}
private void updateForAnimatedDisplay() {
KetPanel ketPanel = document.getKetPanel();
ketPanel.updateForAnimatedDisplay();
}
private void saveAsImage(Equation currentEquation) {
ColourScheme colourScheme = ColourScheme.WHITEBOARD; // TODO: Generalize.
Box currentBox = currentEquation.toBox(colourScheme);
BufferedImage image = ImageTools.boxToBufferedImage(currentBox, 800, 600, colourScheme);
// If image name already ends with ".png", remove it.
imageName = imageName.replace("\\.png$", "");
String name = String.format(imageName+"%d.png", imageNumber++);
Ket.out.println("Writing frame to '" + name + "'");
try {
ImageIO.write(image, "png", new File(name));
} catch (IOException ioe) {
Ket.out.println(" !!! Can't save current screen !!! ");
Ket.out.println(ioe);
Ket.out.println();
Ket.out.println("[Stopping recording any more images to avoid possible cascading failures]");
stopSavingImages();
}
}
/**
* This is a development tool, used to profile relative frequencies of
* commands by saving chords to a unique file (in the data folder).
*/
public void logToFile(Chord chord) {
// Log the ketUI.chord
if (Ket.KEY_LOGGING_ENABLED) {
boolean append;
if (uniqueName==null) {
String randomString = "" + Math.random();
uniqueName = "keys" + randomString.substring(2) + ".log";
Ket.out.printf("unique name = '%s'\n", uniqueName);
append = false;
} else {
append = true;
}
if (uniqueName!=null) {
try {
FileWriter fileWriter = new FileWriter(uniqueName, append);
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
bufferedWriter.write(chord.getSummary() + "\n");
bufferedWriter.close();
} catch (IOException e) {
Ket.out.println("exception = \n" + e);
// Do nothing.
}
}
}
}
private Modes getModes() {
return document.getModes();
}
}