/*
* 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.responder;
import java.util.*;
import ketUI.CommandListener;
import geom.Position;
import ket.MathCollection;
import ket.Message;
import ket.Selection;
import ket.display.box.Box;
import ket.display.box.BoxList;
import ket.display.box.BoxWord;
import ket.math.convert.ArgumentParser;
import ket.math.convert.Interpretation;
import ket.math.purpose.Text;
import ketUI.Clipboard;
import ketUI.Document;
import ketUI.DocumentManager;
import ketUI.Ket;
import ketUI.MouseButton;
import ketUI.MouseEventHandler;
import ketUI.chord.Chord;
import ketUI.chord.KeyboardEventHandler;
import ketUI.chord.Macros;
import ketUI.modes.DocumentState;
import ketUI.modes.Modes;
import ketUI.modes.Search;
import ketUI.panel.KetPanel;
import ket.math.*;
public class StringResponder extends Responder {
final Modes modes;
final MathCollection mathCollection;
public StringResponder(Modes modes) {
this.modes = modes;
this.mathCollection = modes.getMathCollection();
}
private void mapInsert(String string, Clipboard clipboard, KnownArguments knownArguments) {
Argument input = ArgumentParser.parseArgument(string, knownArguments, clipboard, mathCollection);
Function function = knownArguments.toFunction(input.getPurpose());
mapInsert(input, function, getCurrent().asBranch());
}
private void map2Insert(String string, Clipboard clipboard, KnownArguments knownArguments) {
Argument input = ArgumentParser.parseArgument(string, knownArguments, clipboard, mathCollection);
Function function = knownArguments.toFunction(input.getPurpose());
Vector<Argument> children = getCurrent().asBranch().getChildren();
for (Argument child : children) {
//- setCurrent(child);
if (child instanceof Branch) {
mapInsert(input, function, child.asBranch());
}
}
}
private void mapInsert(Argument input, Function function, Branch target) {
boolean append = modes.getDocumentState()==DocumentState.MAP_APPEND;
boolean inputHasKids = input instanceof Branch && input.asBranch().size()>0;
for (Argument child : target.getChildren()) {
Branch intermediate = child.addIntermediaryParent(function);
if (inputHasKids) {
Branch clone = input.asBranch().cloneBranch();
if (append) {
intermediate.appendAll(clone.getChildren());
} else {
intermediate.prependAll(clone.getChildren());
}
}
}
}
private String processNextReplaceChordIterationString(String string, Chord chord) {
if (string.matches("\\s*")) {
modes.error(" !!! Replace responder: cannot replace with a blank string !!! ");
return null;
}
Clipboard clipboard = modes.getClipboard();
KnownArguments knownArguments = mathCollection.getKnownArguments();
switch(modes.getDocumentState()) {
case WRITE_FUNCTION: // &sin
{
Argument next = ArgumentParser.parseArgument(string, knownArguments, clipboard, mathCollection);
if (next==null) break; // <---
Function function = knownArguments.toFunction(next.getPurpose());
if (getCurrent().isBranch()) {
getCurrent().asBranch().setFunction(function);
} else {
getSelection().replace(new Branch(function));
}
modes.getNormalMode().selectNextUnknown(false);
chord.setComplete(false);
modes.setDocumentState(DocumentState.WRITE_MODE);
return null;
}
case WRITE_SYMBOL: // \alpha or ln
{
Argument next = ArgumentParser.parseArgument("\\"+string, knownArguments, clipboard, mathCollection);
if (getCurrent().isBranch()) {
Function function = knownArguments.toFunction(next.getPurpose());
getCurrent().asBranch().setFunction(function);
} else {
getSelection().replace(next);
}
//+ Reverse direction of search/skip/all/done ....
boolean found = modes.getNormalMode().selectNextUnknown(false);
//< if (found) {
System.out.println("[Found]");
chord.setComplete(false);
modes.setDocumentState(DocumentState.WRITE_MODE);
return null;
/*
} else {
System.out.println("[done]");
chord.setComplete(true);
modes.setDocumentState(DocumentState.NORMAL);
}
break;*/
}
case MAP2_PREPEND:
case MAP2_APPEND:
map2Insert(string, clipboard, knownArguments);
break;
case MAP_PREPEND:
case MAP_APPEND:
mapInsert(string, clipboard, knownArguments);
break;
// Replace current with a new purpose and if it is a branch then also add new siblings when given.
case PREPEND_PURPOSE:
case APPEND_PURPOSE:
if (chord!=null) {
chord.setComplete(true);
}
Argument input = ArgumentParser.parseArgument(string, knownArguments, clipboard, mathCollection);
if (getCurrent() instanceof Token) {
State state = knownArguments.toState(input.getPurpose());
getCurrent().asToken().setState(state);
break;
}
Function function = knownArguments.toFunction(input.getPurpose());
Branch currentBranch = (Branch) getCurrent();
currentBranch.setFunction(function);
boolean append = modes.getDocumentState()==DocumentState.APPEND_PURPOSE;
Branch inputBranch = input.asBranch();
if (inputBranch==null || inputBranch.size()==0) {
// Do nothing.
} else if (append) {
currentBranch.appendAll(inputBranch.getChildren());
} else {
currentBranch.prependAll(inputBranch.getChildren());
}
break;
case UPDATE_PREFIX:
Argument prefix = ArgumentParser.parsePrefix(string, knownArguments, clipboard, mathCollection);
getSelection().replace(prefix);
break;
case UPDATE_REPLACE:
Argument replacement = ArgumentParser.parseArgument(string, knownArguments, clipboard, mathCollection);
if (replacement!=null) {
getSelection().replace(replacement);
}
break;
case CLJ_REPLACE:
System.out.println(" --- stringResponder::CLJ_REPLACE --- ");
Argument cljCode = ArgumentParser.parseClj(string, clipboard, mathCollection); // How is mathCollection used?
if (cljCode!=null) {
System.out.println("[code=" + cljCode);
getSelection().replace(cljCode);
}
break;
case UPDATE_SUBSTITUTE:
Argument substitution = ArgumentParser.parseArgument(string, knownArguments, clipboard, mathCollection);
if (substitution!=null) {
getSelection().substitute(substitution);
}
break;
}
return string;
}
private String processNextChordIterationString(String string, Chord chord) {
Document document = modes.getDocument();
switch (modes.getDocumentState()) {
case GET_LABEL_STRING:
if (string.matches("\\s*")) {
// An empty string removes the label.
getCurrent().getEquation().setLabel(null);
string = "";
// A blank string means clear; a null string would imply failure.
} else {
getCurrent().getEquation().setLabel(string);
}
return string;
case SIFT:
if (string.matches("\\s*")) {
modes.error(" !!! Cannot search for whitespace !!! ");
return null;
}
modes.getSearch().search(string, Search.FORWARDS);
// Restrict the view to those lines that matched the search.
Vector<Equation> eqtns = modes.getSearch().getEquationMatches();
if (eqtns!=null) {
document.getKetPanel().setVisibleEquations(eqtns);
}
return string;
case SEARCH_FORWARDS:
if (string.matches("\\s*")) {
modes.error(" !!! Cannot search for whitespace !!! ");
return null;
}
modes.getSearch().search(string, Search.FORWARDS);
return string;
case SEARCH_BACKWARDS:
if (string.matches("\\s*")) {
modes.error(" !!! Cannot search for whitespace !!! ");
return null;
}
modes.getSearch().search(string, Search.BACKWARDS);
return string;
case COMMAND:
/*-
// TODO: If on processing the command, the responder
// changed, don't return to normal responder.
modes.getCommandMode().processCommand(string, chord);
// TODO: Only return the string if command responder worked as expected.
return string;
*/
//- Ket.out.println(" --- TEST: Normal command --- ");
//- document.getKeyboardEventHandler().normal(string);
CommandListener cl = document.getCommandListener();
if (cl!=null) {
cl.ketCommand(string);
} else {
Ket.out.println("[no command listener: '"+string+"']");
}
return string;
case UPDATE_SUBSTITUTE:
case UPDATE_REPLACE:
case CLJ_REPLACE:
case UPDATE_PREFIX:
case PREPEND_PURPOSE:
case APPEND_PURPOSE:
case MAP_PREPEND:
case MAP_APPEND:
case MAP2_PREPEND:
case MAP2_APPEND:
case WRITE_SYMBOL:
case WRITE_FUNCTION:
return processNextReplaceChordIterationString(string, chord);
default:
throw new RuntimeException("Unreachable code.");
}
}
private Argument getCurrent() {
return getSelection().getCurrent();
}
private Selection getSelection() {
// TODO: Store and call mathCollection directly?
return modes.getSelection();
}
private void setCurrent(Argument argument) {
modes.getSelection().setCurrent(argument);
}
private Message getMessage() {
return modes.getMessage();
}
@Override
public void prepareToRespond() {
modes.getMessage().ensureEditingStarted();
}
@Override
public void cleanAfterRespond() {
getMessage().cancelAndClear();
}
/**
* Append the last ketUI.chord's key press to the current message and respond
* appropriately to complete messages.
*/
@Override
public void respondToChordEvent(Chord chord, Macros macros, KeyboardEventHandler keyboardEventHandler) {
// TODO: Merge methods:
Message message = getMessage();
assert modes.getDocumentState()!=DocumentState.UPDATE_TEXT;
chord.appendLastKeypressToMessage(message, false);
if (message.isComplete()) {
String messageString = message.getLine();
String processedMessageString = processNextChordIterationString(messageString, chord);
if (processedMessageString!=null) {
chord.appendMessage(processedMessageString);
//- System.out.println("#1" + chord.isComplete());
//- System.out.println("#1" + modes.getDocumentState());
} else if (modes.getDocumentState()==DocumentState.WRITE_MODE) { // Part of an editing loop [BUG: Strings are discarded].
// Do nothing.
} else {
Ket.out.println(" !!! Null message string !!! ");
chord.setError(true);
}
} else { // Message is in append mode.
// Still in append mode: continue appending characters.
chord.setComplete(false);
}
}
private boolean messageBoxClick(boolean singleClick, Position p) {
Document document = modes.getDocument();
Message message = modes.getMessage();
KetPanel ketPanel = document.getKetPanel();
// box list: (BoxWord) start, (BoxWord) "|", (BoxWord) end
BoxList messageBox = ketPanel.getMessageBoxList();
if (messageBox==null) return false;
Box clickedOn = messageBox.findDeepestBox(p);
boolean messageBoxClick = clickedOn!=null;
if (!messageBoxClick) return false;
Vector<Box> children = messageBox.getChildren();
if (children.size()!=3) return false;
BoxWord startBoxWord = (BoxWord) children.get(0);
BoxWord endBoxWord = (BoxWord) children.get(2);
int index = -1;
if (startBoxWord==clickedOn) {
index = startBoxWord.getIndex(p)-1;
} else if (endBoxWord==clickedOn) {
// Including cursor index.
int indent = message.getStart().length();
index = indent + endBoxWord.getIndex(p) + 1;
} else {
return false;
}
if (index==-1) return false;
message.setIndex(index);
return true;
}
@Override
public void respondToMouseClick(MouseButton mouseButton, boolean singleClick, Position p) {
boolean done = messageBoxClick(singleClick, p);
if (done)
return;
switch (mouseButton) {
case LEFT:
if (singleClick) {
// TODO: Refactor this as soon as possible.
// TODO: Accept any text changes.
// Think about this pivot carefully:
Message message = modes.getMessage();
String messageString = message.getLine();
processNextChordIterationString(messageString, null);
modes.setDocumentState(DocumentState.NORMAL);
} else {
//- mouseEventHandler.edit();
modes.getNormalMode().editCurrentArgument();
}
return;
case MIDDLE:
return;
case RIGHT:
return;
default:
return;
}
}
@Override
public void respondToMouseDrag(MouseButton mouseButton, Position initial, Position release) {
// Do nothing.
}
}