/*
* 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 geom.Position;
import ket.*;
import ket.math.*;
import ket.math.convert.Like;
import ket.math.purpose.Word;
import ketUI.*;
import ketUI.chord.*;
import ketUI.modes.*;
/**
* This handles user interaction while awaiting the input of a single character.
*/
public class LetterResponder extends Responder { // Why not CharResponder?
final Modes modes;
final MathCollection mathCollection;
public LetterResponder(Modes modes) {
this.modes = modes;
this.mathCollection = modes.getMathCollection();
}
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() {
// Do nothing.
}
@Override
public void cleanAfterRespond() {
// Do nothing.
}
private boolean batchUpdate(Chord chord) { // done?
char character = chord.getKeyChar(-1);
String letter = "" + character;
KeyPress keyPress = chord.getLastKeyPress();
System.out.println(" --- update letters ("+letter+") --- ");
System.out.println("\t" + letter);
chord.setComplete(false);
boolean searchBackwards = false;
Function function = getKnownArguments().getFunctionByChar(character);
System.out.println("function = " + function);
if ((keyPress.ctrlPressed() && character=='h') || "<BackSpace>".equals(keyPress.getCode())) {
getSelection().cleanlyRemove(false);
searchBackwards = true;
} else if ((keyPress.ctrlPressed() && character=='d') || "<Delete>".equals(keyPress.getCode())) { // Ctrl-d or <Del>
getSelection().cleanlyRemove(false);
// If the function is defined and the selection is a token and not unknown, assume you want to add an intermediate parent.
} else if ( function!=null && getCurrent().isToken() && ! Like.isUnknown(getCurrent()) ) {
getSelection().addIntermediaryParent(function);
getSelection().appendNewChild();
return true;
} else if ("<Enter>".equals(keyPress.getCode()) && (keyPress.shiftPressed() || keyPress.ctrlPressed())) { // <Ctrl-Enter>
Equation blank = new Equation(new Token(Symbol.UNKNOWN));
getSelection().appendEquation(blank);
} else if (keyPress.ctrlPressed() && character=='s') { // Subsume, Ctrl-s
getSelection().addNewIntermediaryParent();
return true; // *Again*
} else if (keyPress.ctrlPressed() && (character=='F' || character=='o')) {
getSelection().appendNewSibling();
return true; // *Again*
} else if (keyPress.ctrlPressed() && character=='B') {
getSelection().prependNewSibling();
//- searchBackwards = true;
return true; // *Again*
} else if ("<Space>".equals(keyPress.getCode())) {
getSelection().getCursor().selectParent(); //ish
return true; // *Again*
} else if (keyPress.ctrlPressed() && character=='a') {
Branch current = getSelection().addNewIntermediaryParent();
getSelection().appendNewChild();
getSelection().setCurrent(current);
return true; // *Again*
} else if (keyPress.ctrlPressed() && character=='e') {
Branch current = getSelection().addNewIntermediaryParent();
getSelection().prependNewChild();
getSelection().setCurrent(current);
return true; // *Again*
} else if (keyPress.ctrlPressed() && (character=='u' || character=='/')) {
mathCollection.undo();
return true;
} else if (keyPress.ctrlPressed() && character=='f') { // Select next
//< System.out.println("[ctrl-f: forwards]");
//< // Do nothing.
//- getSelection().getCursor().selectLeft();
getSelection().getCursor().selectInOrOutRight();
return true;
} else if (keyPress.ctrlPressed() && character=='b') { // Select backwards
//< System.out.println("[ctrl-b: backwards]");
//- getSelection().getCursor().selectRight();
getSelection().getCursor().selectInOrOutLeft();
return true;
//- searchBackwards = true;
} else if ("<Enter>".equals(keyPress.getCode())) { // Duplicate with Ctrl-j Ctrl-m.
// replace with function
// keep selected
// next time around check for a function and accept numbers not as replacements but as #args
// parse selected functions differently.
} else if (character==':' || character=='&') { // &sin
modes.setDocumentState(DocumentState.WRITE_FUNCTION);
return true; // *Again*
} else if (character=='\\') { // \alpha
// NOTE: Minor bug: Can't insert a null function, but the syntax implies it.
System.out.println("[Switch to string input]");
modes.setDocumentState(DocumentState.WRITE_SYMBOL); // TODO: Rename to clarify it applies only to symbols.
return true; // *Again*
} else if (Character.isDigit(character) ) { // 012...
if (getCurrent().isBranch()) { // abc...012...
//- getSelection().replace(new Token(new Word(letter)));
if (character=='0') { // ?() -> ?.
getSelection().replace(new Token(Symbol.UNKNOWN));
} else { // count->argument number, e.g. 3: ?() -> ?(?,?,?)
for (char c='0'; c<character; c++) {
getCurrent().asBranch().append(new Token(Symbol.UNKNOWN));
}
}
return true; // *Again*
} else {
// TODO: 'f' -> f() not f.
getSelection().replace(new Token(character - '0'));
}
} else if (getCurrent().isToken() && character=='(') { // ?() -> *()
Branch conversion = new Branch(Function.UNKNOWN); // TODO: Reuse stuff from current.state?
getSelection().replace(conversion);
//< conversion.append(new Token(Symbol.UNKNOWN));
return true; // *Again*
} else if (getCurrent().getParentBranch()!=null && character==')') { // ?(?) -> ?(?,?)
getSelection().appendNewSibling();
return true; // *Again*
} else if (getCurrent().isBranch() && function!=null) { // ?() -> *()
System.out.println("[set function " + getCurrent());
getCurrent().asBranch().setFunction(function);
} else if (Character.isLetter(character)) {
if (getCurrent().isBranch()) { // abc...
Function f = getKnownArguments().recordUniqueFunction(letter);
getCurrent().asBranch().setFunction(f);
} else {
getSelection().replace(new Token(new Word(letter)));
}
// other cases
} else {
System.out.println("[error]");
}
//+ Reverse direction of search/skip/all/done ....
boolean found = getNormalMode().selectNextUnknown(searchBackwards);
if (found) {
System.out.println("[Found]");
return true; // *Again*
} else {
System.out.println("[Done]");
//< chord.setComplete(true);
//< return false;
return true;
}
}
@Override
public void respondToChordEvent(Chord chord, Macros macros, KeyboardEventHandler keyboardEventHandler) {
char character = chord.getKeyChar(-1);
String letter = "" + character;
chord.setComplete(true);
KnownArguments knownArguments = keyboardEventHandler.getDocument().getModes().getKnownArguments();
switch (modes.getDocumentState()) {
case WRITE_MODE:
boolean stay = batchUpdate(chord);
mathCollection.updateUndoStack();
if (stay) {
return;
} else {
break;
}
case DELETE:
recognizeDeleteChord(chord, character);
break;
case QUICK_RECURSIVE_DELETE:
System.out.println(" --- quick recursive delete --- ");
System.out.println("chord = " + character);
Argument original = getCurrent();
Argument result = modes.getFind().find(character, Find.FORWARDS, knownArguments); // TODO: Simpliy.
Vector<Argument> matches = new Vector<Argument>();
while (result!=null) {
//- result.remove();
matches.add(result);
System.out.println("removing: " + result);
result = modes.getFind().find(character, Find.FORWARDS, knownArguments);
}
Collections.reverse(matches);
for (Argument m : matches) {
getSelection().setCurrent(m);
getSelection().cleanlyRemove(false);
//? getSelection().remove();
}
if ( ! original.isIn(matches) ) {
setCurrent(original);
}
System.out.println("[done]");
break;
case PASTE_UNITARY:
// TODO: letter -> operation.
Clipboard clipboard = modes.getClipboard();
Argument clipboardArgument = clipboard.getArgument();
Function unitary = getKnownArguments().getFunctionByChar(character);
getSelection().cleanlyInsert(clipboardArgument, unitary);
break;
case PASTE_MAP:
getPasteMode().appendMapFromClipboard();
break;
case PASTE:
recognizePasteChord(chord, character);
break;
case RECORD_MACRO:
macros.startRecordingMacro(letter);
break;
case FIND_FORWARDS:
chord.setLoopSkipped(true);
for (int i=0; i<chord.getCounts(); i++) {
modes.getFind().find(character, Find.FORWARDS, knownArguments);
}
break;
case FIND_BACKWARDS:
chord.setLoopSkipped(true);
for (int i=0; i<chord.getCounts(); i++) {
modes.getFind().find(character, Find.BACKWARDS, knownArguments);
}
break;
case PLAY_MACRO:
int count = 1;
if (chord.countChanged()) {
count = chord.getCounts();
chord.setLoopSkipped(true);
}
for (int i=0; i<count; i++) {
macros.execute(keyboardEventHandler, letter);
}
break;
case SET_MARK:
getEquationList().mark(letter, getCurrent());
break;
case SET_REGISTER:
modes.getClipboard().setRegister(letter);
break;
case MOVE_TO_MARK:
Argument mark = getEquationList().getMark(letter);
if (mark!=null) {
setCurrent(mark);
} // Otherwise, give up.
break;
}
modes.setDocumentState(DocumentState.NORMAL);
}
private EquationList getEquationList() {
return getCurrent().getEquationList();
}
// TODO: The delete methods that are called here should be mode
// agnostic, ie don't add letter-mode, but don't use normal mode
// either.
private boolean recognizeDeleteChord(Chord chord, char character) {
chord.setComplete(true);
if (Character.isDigit(character) && character!='0') { // Delete e.g. the 3rd argument, 'd3'.
Branch currentBranch = getCurrent().asBranch();
if (currentBranch==null) return false;
int index = (int)character - (int)'0' - 1;
int size = currentBranch.size();
if (index>=size) {
//-? index = size-1;
return false;
}
Argument child = currentBranch.getChild(index);
Ket.out.println("child = " + child);
switch(size) {
case 0: // f() -> f()
return false;
case 1: // f(a) -> f
child.remove();
return true;
case 2: // f(a,b) -> b
getSelection().replace(child.getOnlySibling());
return true;
default: // f(a,b,c) -> f(b,c)
child.remove();
return true;
}
}
KeyPress keyPress = chord.getLastKeyPress();
String code = keyPress.getCode();
switch (code) {
case "<Left>": // d<Left> Delete left sibling.
return getDeleteMode().deleteLeft();
case "<Right>": // d<Right> Delete right sibling.
return getDeleteMode().deleteRight();
case "<Up>": // d<Up> Delete this and the next line.
getDeleteMode().deleteUp();
return true;
case "<Down>": // d<Up> Delete this and the next line.
getDeleteMode().deleteDown();
return true;
}
switch(character) {
case 's': // 'ds' Delete every match of the current selection within the current equation.
Vector<Argument> args = new ArgumentVector(getCurrent().getVisibleRoot(), ArgumentVector.INCLUDE_ROOT);
//- Collections.reverse(args);
EquationList el = getEquationList();
Argument current = getCurrent();
Equation e = current.getEquation();
Argument match = getCurrent().cloneArgument();
for (Argument a : args) {
if (a.getEquationList()!=el) continue;
if (!match.equals(a)) continue;
getSelection().setCurrent(a);
getSelection().cleanlyRemove(false);
}
if (getSelection().isIn(current)) {
getSelection().setCurrent(current);
}
return true;
// /////////////////////////
case 'i': // 'di' Delete leftmost child argument.
return getDeleteMode().deleteInLeft();
case 'o': // 'do' Delete rightmost child argument.
return getDeleteMode().deleteInRight();
case 'h': // 'dh' Delete left sibling.
return getDeleteMode().deleteLeft();
case 'l': // 'dl' Delete right sibling.
return getDeleteMode().deleteRight();
case ' ': // 'd ' Delete the parent function.
return getDeleteMode().deleteOut();
case 'k': // 'dk' Delete this and the next line.
getDeleteMode().deleteUp();
return true;
case 'j': // 'dj' Delete this and the next line.
getDeleteMode().deleteDown();
return true;
// /////////////////////////
case 't': // 'dt' Remove any trace of the current argument, storing it and its parent in clipboard (without the current siblings).
getSelection().deleteTrace();
return true;
case 'd': // 'dd' Delete the current equation.
case 'e': // 'de' Delete the current equation.
return getDeleteMode().deleteCurrentEquation();
case 'p': // 'dp' Delete the current branch's parent while retaining the current argument.
return getDeleteMode().deleteParent();
case '0': // 'd0' Delete the current branch (argument and all its children).
case '.': // 'd.' Delete the current branch (argument and all its children).
case 'x': // 'dx' Delete the current branch (argument and all its children).
case 'b': // 'db' Delete the current branch (argument and all its children).
getDeleteMode().remove();
return true;
case 'c': // 'dc' Delete the current intermediary argument, but retain any children.
getDeleteMode().deleteIntermediate();
return true;
case 'm': // 'dm' Remove-map, i.e. delete all intermediate functions of children.
getDeleteMode().mapDeleteIntermediate();
return false;
case 'H':
{
Argument next = getCurrent().getNextSibling();
if (next==null) return false;
setCurrent(next);
getSelection().deleteTrace();
return true;
}
case 'L':
{
Argument prev = getCurrent().getPreviousSibling();
if (prev==null) return false;
setCurrent(prev);
getSelection().deleteTrace();
return true;
}
/*-
default:
*/
}
Function function = getKnownArguments().getFunctionByChar(character);
if (function==null) {
Ket.out.println(" !!! Unknown command: 'd"+character+"' !!! ");
return false;
}
int settings = ArgumentVector.BRANCHES_ONLY|ArgumentVector.INCLUDE_ROOT|ArgumentVector.REVERSE_ITERATOR_ORDER;
ArgumentVector argumentVector = new ArgumentVector(getCurrent(), settings);
for (Branch branch : argumentVector.toBranchVector()) {
if (branch.getFunction()==function) {
getSelection().setCurrent(branch);
getSelection().cleanlyRemove(false);
}
}
return true;
}
private boolean recognizePasteChord(Chord chord, char character) {
if (Character.isDigit(character) && character!='0') { // Paste e.g. the 3rd argument, 'p3'.
Branch currentBranch = getCurrent().asBranch();
int index = -1;
if (currentBranch!=null) {
index = (int)character - (int)'0' - 2;
int size = currentBranch.size();
if (index>=size) {
index = size-1;
}
}
getPasteMode().addChildAfterFromClipboard(index);
}
KeyPress keyPress = chord.getLastKeyPress();
String code = keyPress.getCode();
switch (code) {
case "<Left>": // p<Left> Paste to the left of the current argument, i.e. as the previous (left) sibling.
return getPasteMode().prependSiblingFromClipboard();
case "<Right>": // p<Right> Paste to the right of the current argument, i.e. as the next (right) sibling.
return getPasteMode().appendSiblingFromClipboard();
case "<Up>": // p<Up> Paste to a new equation above the current equation.
return getPasteMode().prependUpFromClipboard();
case "<Down>": // p<Up> Paste to a new equation below the current equation.
return getPasteMode().appendDownFromClipboard();
}
switch(character) {
case '"': // 'p"' Substitute for the contents of clipboard in the current argument.
return getPasteMode().appendSubstituteFromClipboard();
case 't': // 'pt' Paste as text.
getPasteMode().pasteTextFromClipboard();
return true;
case 'p': // 'pp' Insert a new parent from clipboard.
case ' ': // 'p<Space>' Paste 'up', i.e. insert current into the pasted sub-branch.
//- return getPasteMode().pasteUp();
return getPasteMode().appendParentFromClipboard();
case '.': // 'p.' Replace the entire current branch (i.e. the current sub-tree).
case '0': // 'p0' Replace the entire current branch (i.e. the current sub-tree).
case 'b': // 'pb' Replace the entire current branch (i.e. the current sub-tree).
case 'r': // 'pr' Replace the entire current branch (i.e. the current sub-tree).
return getPasteMode().appendBranchFromClipboard();
case 'l': // 'pl' Paste to the right of the current argument, i.e. as the next (right) sibling.
return getPasteMode().appendSiblingFromClipboard();
case 'm': // 'pm' Paste map, i.e. apply the clipoboard argument as an intermediate function to all of selection's children.
Ket.out.println(" --- paste map --- ");
return getPasteMode().appendMapFromClipboard();
case 'h': // 'ph' Paste to the left of the current argument, i.e. as the previous (left) sibling.
return getPasteMode().prependSiblingFromClipboard();
case 'i': // 'pi' Prepend child from clipboard.
return getPasteMode().prependChildFromClipboard();
case 'o': // 'po' Append child from clipboard.
return getPasteMode().appendChildFromClipboard();
case '~': // 'p~' Insert the inverse of clipboard's function as a new parent.
return getPasteMode().appendInverseParentFromClipboard();
case 's': // 'ps' Replace the current symbol of the current function with that of the clipboard argument.
return getPasteMode().appendPurposeFromClipboard();
case 'c': // 'pc' While retaining the current argument's children, replace it with the root argument in clipboard.
return getPasteMode().appendCurrentFromClipboard();
case 'k': // 'pk' Paste to a new equation above the current equation.
return getPasteMode().prependUpFromClipboard();
case 'j': // 'pj' Paste to a new equation below the current equation.
return getPasteMode().appendDownFromClipboard();
case 'e': // 'pe' Paste to a new equation below the current equation.
return getPasteMode().appendEquationFromClipboard();
}
/////////////////////////////////////////////////
// ADD FUNCTION AND PASTE AS THE NEXT ARGUMENT //
/////////////////////////////////////////////////
Function function = getKnownArguments().getFunctionByChar(character);
if (function!=null) {
return getPasteMode().appendWithFunctionFromClipboard(function);
} else {
Ket.out.println(" !!! Unknown command: 'p"+character+"' !!! ");
return false;
}
}
@Override
public void respondToMouseClick(MouseButton mouseButton, boolean singleClick, Position p) {
// Do nothing.
}
@Override
public void respondToMouseDrag(MouseButton mouseButton, Position initial, Position release) {
// Do nothing.
}
private NormalMode getNormalMode() {
return modes.getNormalMode();
}
private PasteMode getPasteMode() {
return modes.getPasteMode();
}
private AddMode getAddMode() {
return modes.getAddMode();
}
private DeleteMode getDeleteMode() {
return modes.getDeleteMode();
}
private KnownArguments getKnownArguments() {
return mathCollection.getKnownArguments();
}
private Cursor getCursor() {
return mathCollection.getCursor();
}
}