/*
* 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.Vector;
import java.awt.event.KeyEvent;
import geom.Position;
import ket.Cursor;
import ket.MathCollection;
import ket.Message;
import ket.RangeSelection;
import ket.Selection;
import ketUI.Ket;
import ketUI.MouseButton;
import ketUI.MouseEventHandler;
import ketUI.panel.KetPanel;
import ket.math.*;
import ket.math.purpose.*;
import ketUI.chord.*;
import ketUI.modes.*;
/**
* Some add chords are qualified with characters such as directions or
* parent-branch functions and these are handled together here.
*/
public class AddResponder extends Responder {
final boolean ADD_INTERMEDIATE = true;
final boolean APPEND = true;
final boolean SLOW = true;
final Modes modes;
final MathCollection mathCollection;
public AddResponder(Modes modes) {
this.modes = modes;
this.mathCollection = modes.getMathCollection();
}
@Override
public void prepareToRespond() {
// Do nothing.
}
@Override
public void cleanAfterRespond() {
// Do nothing.
}
public void respondToChordEvent(Chord chord, Macros macros, KeyboardEventHandler keyboardEventHandler) {
KeyPress keyPress = chord.getLastKeyPress();
char character = chord.getKeyChar(-1);
//- KnownArguments knownArguments = keyboardEventHandler.getDocument().getModes().getKnownArguments();
boolean changed = false;
switch (modes.getDocumentState()) {
case MAP_QUICK_PARENT:
if (getCurrent().isToken()) return;
for (Argument child : getSelection().getCurrent().asBranch().getChildren()) {
getSelection().setCurrent(child); //ish
insertFromCharacter(chord, keyPress, character, ADD_INTERMEDIATE);
}
break;
case QUICK_PARENT:
insertFromCharacter(chord, keyPress, character, ADD_INTERMEDIATE);
break;
case QUICK_RENAME:
insertFromCharacter(chord, keyPress, character, !ADD_INTERMEDIATE);
break;
case APPEND_BY_STRING:
insertWithCharacter(chord, keyPress, character, APPEND, SLOW);
break;
case PREPEND_BY_STRING:
insertWithCharacter(chord, keyPress, character, !APPEND, SLOW);
break;
case APPEND_BY_LETTER:
insertWithCharacter(chord, keyPress, character, APPEND, !SLOW);
break;
case PREPEND_BY_LETTER:
insertWithCharacter(chord, keyPress, character, !APPEND, !SLOW);
break;
default:
modes.setDocumentState(DocumentState.NORMAL);
}
}
/**
* A range of different insertions can be identified by a character
* specifying either a direction or function. This method inserts a
* required argument given the direction and changes to the appropriate
* mode, possibly awaiting further input.
*/
private boolean insertWithCharacter(Chord chord, KeyPress keyPress, char character, boolean append, boolean slow) {
if (Character.isDigit(character)) {
int value = Integer.parseInt(""+character);
IntegerValue integerValue = new IntegerValue(value);
Function function = append ? Function.POWER : Function.TIMES;
getSelection().addIntermediaryParent(function);
mathCollection.setCursorSelection();
if (append) {
// Integer power
Argument exponent = new Token(integerValue);
getSelection().appendChild(exponent);
} else {
// Integer multiple
Argument term = new Token(integerValue);
getSelection().prependChild(term);
}
chord.setComplete(true);
modes.setDocumentState(DocumentState.NORMAL);
return true;
}
boolean success = insertByDirection(chord, keyPress, character, append, slow);
if (success) {
return true;
}
Function function = getKnownArguments().getFunctionByChar(character);
if (function==null) {
String letter = append ? "a" : "e";
Ket.out.println(" !!! Unknown command: '"+letter+""+character+"' !!! ");
return false;
}
boolean result = insertByFunction(chord, append, function, slow);
getSelection().getCurrent().unfoldRoots();
return result;
}
public KetPanel getKetPanel() {
return modes.getDocument().getKetPanel();
}
/**
* If the given character is recognised as a 'direction' such as left,
* right, in, up, equation; then a new argument is added as
* appropriate.
*/
public boolean insertByDirection(Chord chord, KeyPress keyPress, char character, boolean isSiblingAppended, boolean slow) {
String code = keyPress.getCode();
switch (code) {
// INFORMAL: Should be 'a<Shift-Left>' but 'feels' wrong.
case "<Left>": // p<Left> Add a new left sibling.
getAddMode().prependNewSibling(chord);
if ( ! slow ) {
modes.setDocumentState(DocumentState.QUICK_RENAME);
}
return true;
case "<Right>": // p<Right> Add a new right sibling.
getAddMode().appendNewSibling(chord);
if ( ! slow ) {
modes.setDocumentState(DocumentState.QUICK_RENAME);
}
return true;
case "<Up>": // p<Up> Add a new equation before the current equation.
getAddMode().prependUp(chord);
if ( ! slow ) {
modes.setDocumentState(DocumentState.QUICK_RENAME);
}
return true;
case "<Down>": // p<Up> Append a new equation.
getAddMode().appendDown(chord);
if ( ! slow ) {
modes.setDocumentState(DocumentState.QUICK_RENAME);
}
return true;
case "<Space>": // 'a<Space>' Add blank line.
Token token = new Token(new Text(""));
Equation equation = new Equation(token);
if (isSiblingAppended) {
getSelection().appendEquation(equation);
} else {
getSelection().prependEquation(equation);
}
chord.setComplete(true);
modes.setDocumentState(DocumentState.NORMAL);
return true;
}
// Cases where the choice of 'a...' or 'e...' matters.
if (isSiblingAppended) { // Append ('a...').
switch(character) {
case 'e': // 'ae' Append a new equation.
getAddMode().appendEquation(chord);
if ( ! slow ) {
modes.setDocumentState(DocumentState.QUICK_RENAME);
}
return true;
case 't': // 'at' Append a line of text.
return getAddMode().appendTextLine(chord);
case 'm': // 'am' Append children with an intermediate branch.
return getAddMode().mapAppend(chord);
case 'n': // 'an' Append grandchildren.
return getAddMode().map2Append(chord);
case 'p': // Add parent.
//- return getAddMode().addIntermediaryParent(chord);
getAddMode().appendPurpose(chord); // <--- !slow?
return true;
}
} else { // Prepend ('e...').
switch(character) {
case 'e': // 'ee' Prepend a new equation.
getAddMode().prependEquation(chord);
if ( ! slow ) {
modes.setDocumentState(DocumentState.QUICK_RENAME);
}
return true;
case 't': // 'at' Append a line of text.
return getAddMode().prependTextLine(chord);
case 'm': // 'em' Prepend children with an intermediate branch.
return getAddMode().mapPrepend(chord); // <--- !slow?
case 'n': // 'en' Prepend grandchildren.
return getAddMode().map2Prepend(chord);
case 'p': // Add parent.
return getAddMode().prependPurpose(chord); // <--- !slow?
}
}
// TODO: Split this into 'a...' and 'e...' apart for specific commands.
switch(character) {
case '"': // 'a"' Replace selection as text.
return getAddMode().replaceTextLine(chord);
case '.': // 'a." Replace current argument.
return getNormalMode().replaceCurrentArgument(chord);
case 'a': // 'aa" Replace current equation.
getCursor().selectVisibleRoot();
return getNormalMode().replaceCurrentArgument(chord);
case 'j': // 'aj' Append a new equation.
getAddMode().appendDown(chord);
if ( ! slow ) {
modes.setDocumentState(DocumentState.QUICK_RENAME);
}
return true;
case 'k': // 'ak' Add a new equation before the current equation.
getAddMode().prependUp(chord);
if ( ! slow ) {
modes.setDocumentState(DocumentState.QUICK_RENAME);
}
return true;
case 'h': // 'ah' Add a new left sibling.
getAddMode().prependNewSibling(chord);
if ( ! slow ) {
modes.setDocumentState(DocumentState.QUICK_RENAME);
}
return true;
case 'l': // 'al' Add a new right sibling.
getAddMode().appendNewSibling(chord);
if ( ! slow ) {
modes.setDocumentState(DocumentState.QUICK_RENAME);
}
return true;
case 'i': // 'ai' Add a new left child to the current function.
getAddMode().prependNewChild(chord);
if ( ! slow ) {
modes.setDocumentState(DocumentState.QUICK_RENAME);
}
return true;
case 'o': // 'ao' Append a new child to the right of the current argument.
getAddMode().appendNewChild(chord);
if ( ! slow ) {
modes.setDocumentState(DocumentState.QUICK_RENAME);
}
return true;
default:
// This is an exception: the chord need not be
// complete on leaving this method!
return false;
}
}
/**
* Add a new intermediate parent branch of the given function and
* append/prepend a new sibling to be specified as a string (slow) or
* character (otherwise).
*/
public boolean insertByFunction(Chord chord, boolean isSiblingAppended, Function function, boolean slow) {
boolean multiple = mathCollection.isRangeSelectionSet();
if (multiple) {
Parent currentParent = getCurrent().getParent();
if (currentParent instanceof Branch) {
Branch parentBranch = (Branch) currentParent;
Function parentFunction = parentBranch.getFunction();
getSelection().addIntermediaryParent(parentFunction);
}
}
getSelection().addIntermediaryParent(function);
mathCollection.setCursorSelection();
if (isSiblingAppended) {
getSelection().appendNewChild();
} else {
getSelection().prependNewChild();
}
if (slow) {
modes.setDocumentState(DocumentState.UPDATE_REPLACE);
} else {
modes.setDocumentState(DocumentState.QUICK_RENAME);
}
chord.setComplete(false);
return true;
}
/**
* Insert a new branch with the function type specified by character.
*/
private boolean insertFromCharacter(Chord chord, KeyPress keyPress, char character, boolean addIntermediate) {
chord.setComplete(true);
boolean multiple = mathCollection.isRangeSelectionSet();
if (character=='"' && multiple) {
RangeSelection range = (RangeSelection) getSelection();
Vector<Argument> roots = range.getSubbranchRoots();
String string = "";
for (Argument a : roots) {
Purpose p = getCurrent().getPurpose();
if (p instanceof Value) {
string += ((Value) p).getValue(); // i.e. unquoted
} else {
string += p.toString();
}
}
Token replacement = new Token(new Text(string));
if (range.areMultipleEquationsSelected()) {
// Remove all but one equation and replace it.
Vector<Equation> selectedEquations = range.getSelectedEquations();
range.setOnly(selectedEquations.firstElement().getRoot());
for (int i=1; i<selectedEquations.size(); i++) {
selectedEquations.get(i).removeEquation();
}
} else {
// Remove all but one roots and replace it with:
Vector<Argument> subbranchRoots = range.getSubbranchRoots();
range.setOnly(subbranchRoots.firstElement());
for (int i=1; i<subbranchRoots.size(); i++) {
subbranchRoots.get(i).remove();
}
}
getSelection().replace(replacement); // Now a cursor selection.
return true;
}
// Replace a token's state?
if (getCurrent() instanceof Token && !multiple && !addIntermediate) {
return quickReplaceState(chord, character);
}
// Otherwise replace the function of a branch:
Branch original = null;
Function replacement = replaceBySpecialCharacter(character, addIntermediate, multiple);
if (addIntermediate || multiple) {
// Adds intermediate as requested or to ensures a common parent.
// TODO: In multiple selection model use the first root's parent function.
original = getSelection().addIntermediaryParent(null);
} else {
original = getCursor().asBranch();
}
if (replacement==null) {
// Be clearer when you use '_' to replace an argument with a letter.
Argument substitution = new Token(new Word("" + character));
original.replace(substitution);
setOnly(substitution);
return true;
}
original.unfoldRoots();
if (original!=null) {
original.setFunction(replacement);
return true;
} else {
Ket.out.println(" !!! Can't add modify a token's function !!!");
return false;
}
}
// Look for special function types:
public Function replaceBySpecialCharacter(char character, boolean addIntermediate, boolean multiple) {
switch (character) {
case '~':
Function function = getReferenceFunction(addIntermediate, multiple);
//- return function!=null ? function.getInverse() : null;
return function!=null ? Type.getInverse(function) : null;
case ' ':
return getReferenceFunction(addIntermediate, multiple);
default:
return getKnownArguments().getFunctionByChar(character);
}
}
// Identify an original function for future reference.
private Function getReferenceFunction(boolean addIntermediate, boolean multiple) {
if (multiple) {
// TODO: Move this part to multiple selection model?
RangeSelection msm = mathCollection.getRangeSelection();
ArgumentRange argumentRange = msm.getArgumentRange();
Vector<Argument> roots = argumentRange.getSubbranchRoots();
if (roots!=null && roots.size()>=1) {
Argument first = roots.firstElement();
Parent parent = first.getParent();
if (parent instanceof Branch) {
return ((Branch) parent).getFunction();
}
}
return null;
} else {
Branch branch = getCursor().asBranch();
return branch!=null ? branch.getFunction() : null;
}
}
public boolean quickReplaceState(Chord chord, char character) {
// Replace token with a number, letter or a symbol.
if (Character.isDigit(character)) {
// TODO: This code isn't reachable as
// all normal-mode numbers are
// interpreted as part of the count
// rather than only those preceding
// non-numbers: Fix this.
int value = Integer.parseInt(""+character);
IntegerValue integerValue = new IntegerValue(value);
//- currentToken.setState(integerValue);
getSelection().replace(new Token(integerValue));
return true;
} else if (Character.isLetter(character)) {
Word word = new Word(""+character);
getSelection().replace(new VariableToken(word));
//- getCursor().validate();
return true;
} else {
getSelection().replace(new VariableToken(Symbol.UNKNOWN));
return true;
}
}
@Override
public void respondToMouseClick(MouseButton mouseButton, boolean singleClick, Position p) {
}
@Override
public void respondToMouseDrag(MouseButton mouseButton, Position initial, Position release) {
}
// --------------------------- KEEP THE FOLLOWING? ---------------------------
private KnownArguments getKnownArguments() {
return mathCollection.getKnownArguments();
}
private Argument getCurrent() {
return getSelection().getCurrent();
}
private void setOnly(Argument selection) {
getSelection().setOnly(selection);
}
private Selection getSelection() {
return modes.getSelection();
}
private AddMode getAddMode() {
return modes.getAddMode();
}
private NormalMode getNormalMode() {
return modes.getNormalMode();
}
private Cursor getCursor() {
return mathCollection.getCursor();
}
}