package com.himamis.retex.editor.share.controller; import java.util.ArrayList; import com.google.j2objc.annotations.Weak; import com.himamis.retex.editor.share.editor.MathField; import com.himamis.retex.editor.share.event.KeyEvent; import com.himamis.retex.editor.share.meta.MetaArray; import com.himamis.retex.editor.share.meta.MetaCharacter; import com.himamis.retex.editor.share.meta.MetaFunction; import com.himamis.retex.editor.share.meta.MetaModel; import com.himamis.retex.editor.share.meta.MetaModelArrays; import com.himamis.retex.editor.share.model.MathArray; import com.himamis.retex.editor.share.model.MathCharacter; import com.himamis.retex.editor.share.model.MathComponent; import com.himamis.retex.editor.share.model.MathContainer; import com.himamis.retex.editor.share.model.MathFunction; import com.himamis.retex.editor.share.model.MathSequence; @SuppressWarnings("javadoc") public class InputController { public static final char FUNCTION_OPEN_KEY = '('; // probably universal public static final char FUNCTION_CLOSE_KEY = ')'; public static final char DELIMITER_KEY = ';'; private MetaModel metaModel; @Weak private MathField mathField; private boolean createFrac = true; public InputController(MetaModel metaModel) { this.metaModel = metaModel; } public MathField getMathField() { return mathField; } public void setMathField(MathField mathField) { this.mathField = mathField; } public boolean getCreateFrac() { return createFrac; } public void setCreateFrac(boolean createFrac) { this.createFrac = createFrac; } final static private char getLetter(MathComponent component) throws Exception { if (!(component instanceof MathCharacter)) { throw new Exception("Math component is not a character"); } MathCharacter mathCharacter = (MathCharacter) component; if (!mathCharacter.isCharacter()) { throw new Exception("Math component is not a character"); } char c = mathCharacter.getUnicode(); if (!Character.isLetter(c)) { throw new Exception("Math component is not a character"); } return c; } /** * Insert array. */ public MathArray newArray(EditorState editorState, int size, char arrayOpenKey) { MathSequence currentField = editorState.getCurrentField(); int currentOffset = editorState.getCurrentOffset(); MetaArray meta = metaModel.getArray(arrayOpenKey); MathArray array = new MathArray(meta, size); ArrayList<MathComponent> removed = cut(currentField, currentOffset, -1, editorState, array, true); // add sequence MathSequence field = new MathSequence(); array.setArgument(0, field); insertReverse(field, -1, removed); for (int i = 1; i < size; i++) { // add sequence array.setArgument(i, new MathSequence()); } editorState.resetSelection(); // set current editorState.setCurrentField(field); editorState.setCurrentOffset(field.size()); return array; } /** * Insert matrix. */ public void newMatrix(EditorState editorState, int columns, int rows) { MathSequence currentField = editorState.getCurrentField(); int currentOffset = editorState.getCurrentOffset(); MetaArray meta = metaModel.getMatrix(); MathArray matrix = new MathArray(meta, columns, rows); currentField.addArgument(currentOffset, matrix); // add sequence MathSequence field = new MathSequence(); matrix.setArgument(0, field); for (int i = 1; i < matrix.size(); i++) { // add sequence matrix.setArgument(i, new MathSequence()); } // set current editorState.setCurrentField(field); editorState.setCurrentOffset(0); } /** * Insert braces (), [], {}, "". */ public void newBraces(EditorState editorState, char ch) { String casName = ArgumentHelper.readCharacters(editorState); if (ch == FUNCTION_OPEN_KEY && metaModel.isGeneral(casName)) { delCharacters(editorState, casName.length()); newFunction(editorState, casName); } else if (ch == FUNCTION_OPEN_KEY && metaModel.isFunction(casName)) { delCharacters(editorState, casName.length()); newFunction(editorState, casName); } else { String selText = editorState.getSelectedText().trim(); if(editorState.getSelectionStart() instanceof MathCharacter){ if (selText.startsWith("<") && selText.endsWith(">")) { deleteSelection(editorState); MetaArray meta = metaModel.getArray(ch); MathArray array = new MathArray(meta, 1); MathSequence seq = new MathSequence(); array.setArgument(0, seq); editorState.getCurrentField() .addArgument(editorState.getCurrentOffset(), array); editorState.setCurrentField(seq); editorState.setCurrentOffset(0); return; } } // TODO brace type newArray(editorState, 1, ch); } } /** * Insert function by name. * * @param name * function */ public void newFunction(EditorState editorState, String name) { newFunction(editorState, name, 0); } /** * Insert function by name. * * @param name * function */ public void newFunction(EditorState editorState, String name, int initial) { MathSequence currentField = editorState.getCurrentField(); int currentOffset = editorState.getCurrentOffset(); // add extra braces for sqrt, nthroot and fraction if ("^".equals(name) && currentOffset > 0 && editorState.getSelectionEnd() == null) { if (currentField .getArgument(currentOffset - 1) instanceof MathFunction) { MathFunction function = (MathFunction) currentField .getArgument(currentOffset - 1); if ("sqrt".equals(function.getName()) || "nroot".equals(function.getName()) || "frac".equals(function.getName())) { currentField.delArgument(currentOffset - 1); // add braces MathArray array = new MathArray( metaModel.getArray(MetaArray.REGULAR), 1); currentField.addArgument(currentOffset - 1, array); // add sequence MathSequence field = new MathSequence(); array.setArgument(0, field); field.addArgument(0, function); } } } // add function MathFunction function; if (metaModel.isGeneral(name)) { MetaFunction meta = metaModel.getGeneral(name); function = new MathFunction(meta); } else { MetaFunction meta = metaModel.getFunction(name); function = new MathFunction(meta); } // add sequences for (int i = 0; i < function.size(); i++) { MathSequence field = new MathSequence(); function.setArgument(i, field); } // pass characters for fraction and factorial only if ("frac".equals(name)) { if (editorState.getSelectionEnd() != null) { ArrayList<MathComponent> removed = cut(currentField, currentOffset, -1, editorState, function, true); MathSequence field = new MathSequence(); function.setArgument(0, field); insertReverse(field, -1, removed); editorState.resetSelection(); editorState.setCurrentField(function.getArgument(1)); editorState.setCurrentOffset(0); return; } ArgumentHelper.passArgument(editorState, function); } else if ("^".equals(name)) { if (editorState.getSelectionEnd() != null) { MathArray array = this.newArray(editorState, 1, '('); editorState .setCurrentField((MathSequence) array .getParent()); editorState.resetSelection(); editorState.setCurrentOffset( array.getParentIndex() + 1); newFunction(editorState, name, initial); return; } } else { if (editorState.getSelectionEnd() != null) { ArrayList<MathComponent> removed = cut(currentField, currentOffset, -1, editorState, function, true); MathSequence field = new MathSequence(); function.setArgument(0, field); insertReverse(field, -1, removed); editorState.resetSelection(); editorState.incCurrentOffset(); return; } } currentOffset = editorState.getCurrentOffset(); currentField.addArgument(currentOffset, function); if (function.hasChildren()) { // set current sequence CursorController.firstField(editorState, function.getArgument(initial)); editorState.setCurrentOffset(editorState.getCurrentField().size()); } else { editorState.incCurrentOffset(); } } public void newScript(EditorState editorState, String script) { MathSequence currentField = editorState.getCurrentField(); if (currentField.size() == 0 && currentField.getParent() instanceof MathFunction && "^".equals( ((MathFunction) currentField.getParent()).getName()) && "^".equals(script)) { return; } int currentOffset = editorState.getCurrentOffset(); int offset = currentOffset; while (offset > 0 && currentField .getArgument(offset - 1) instanceof MathFunction) { MathFunction function = (MathFunction) currentField .getArgument(offset - 1); if (script.equals(function.getName())) { editorState.setCurrentField(function.getArgument(0)); editorState.setCurrentOffset(function.getArgument(0).size()); return; } if (!"^".equals(function.getName()) && !"_".equals(function.getName())) { break; } offset--; } offset = currentOffset; while (offset < currentField.size() && currentField.getArgument(offset) instanceof MathFunction) { MathFunction function = (MathFunction) currentField .getArgument(offset); if (script.equals(function.getName())) { editorState.setCurrentField(function.getArgument(0)); editorState.setCurrentOffset(0); return; } if (!"^".equals(function.getName()) && !"_".equals(function.getName())) { break; } offset++; } if (currentOffset > 0 && currentField .getArgument(currentOffset - 1) instanceof MathFunction) { MathFunction function = (MathFunction) currentField .getArgument(currentOffset - 1); if ("^".equals(function.getName()) && "_".equals(script)) { currentOffset--; } } if (currentOffset < currentField.size() && currentField .getArgument(currentOffset) instanceof MathFunction) { MathFunction function = (MathFunction) currentField .getArgument(currentOffset); if ("_".equals(function.getName()) && "^".equals(script)) { currentOffset++; } } editorState.setCurrentOffset(currentOffset); newFunction(editorState, script); } /** * Insert operator. */ public void newOperator(EditorState editorState, char op) { MetaCharacter meta = metaModel.getOperator("" + op); newCharacter(editorState, meta); } /** * Insert symbol. * * @param editorState * state * @param sy * char */ public void newSymbol(EditorState editorState, char sy) { MetaCharacter meta = metaModel.getSymbol("" + sy); newCharacter(editorState, meta); } /** * Insert character. * * @param editorState * state * @param ch * char */ public void newCharacter(EditorState editorState, char ch) { MetaCharacter meta = metaModel.getCharacter("" + ch); newCharacter(editorState, meta); } /** * Insert character. * * @param editorState * current state * @param meta * character */ public static void newCharacter(EditorState editorState, MetaCharacter meta) { editorState.addArgument(new MathCharacter(meta)); } /** * Insert field. * * @param editorState * current state * @param ch * bracket */ public void endField(EditorState editorState, char ch) { MathSequence currentField = editorState.getCurrentField(); int currentOffset = editorState.getCurrentOffset(); // first array specific ... if (currentField.getParent() instanceof MathArray) { MathArray parent = (MathArray) currentField.getParent(); // if ',' typed within 1DArray or Vector ... add new field if (ch == parent.getFieldKey() && (parent.is1DArray() || parent.isVector())) { int index = currentField.getParentIndex(); MathSequence field = new MathSequence(); parent.addArgument(index + 1, field); while (currentField.size() > currentOffset) { MathComponent component = currentField .getArgument(currentOffset); currentField.delArgument(currentOffset); field.addArgument(field.size(), component); } currentField = field; currentOffset = 0; // if ',' typed at the end of intermediate field of 2DArray or // Matrix ... move to next field } else if (ch == parent.getFieldKey() && currentOffset == currentField.size() && parent.size() > currentField.getParentIndex() + 1 && (currentField.getParentIndex() + 1) % parent.columns() != 0) { currentField = parent .getArgument(currentField.getParentIndex() + 1); currentOffset = 0; // if ';' typed at the end of last field ... add new row } else if (ch == parent.getRowKey() && currentOffset == currentField.size() && parent.size() == currentField.getParentIndex() + 1) { parent.addRow(); currentField = parent .getArgument(parent.size() - parent.columns()); currentOffset = 0; // if ';' typed at the end of (not last) row ... move to next // field } else if (ch == parent.getRowKey() && currentOffset == currentField.size() && (currentField.getParentIndex() + 1) % parent.columns() == 0) { currentField = parent .getArgument(currentField.getParentIndex() + 1); currentOffset = 0; // if ']' '}' typed at the end of last field ... move out of // array } else if (ch == parent.getCloseKey() && parent.isArray()) { ArrayList<MathComponent> removed = cut(currentField, currentOffset); insertReverse(parent.getParent(), parent.getParentIndex(), removed); currentOffset = parent.getParentIndex() + 1; currentField = (MathSequence) parent.getParent(); } else if ((ch == parent.getCloseKey() && parent.isMatrix()) && parent.size() == currentField.getParentIndex() + 1 && currentOffset == currentField.size()) { currentOffset = parent.getParentIndex() + 1; currentField = (MathSequence) parent.getParent(); } // now functions, braces, apostrophes ... } else if (currentField.getParent() != null) { MathContainer parent = currentField.getParent(); // if ',' typed at the end of intermediate field of function ... // move to next field if (ch == ',' && currentOffset == currentField.size() && parent instanceof MathFunction && parent.size() > currentField.getParentIndex() + 1) { currentField = (MathSequence) parent .getArgument(currentField.getParentIndex() + 1); currentOffset = 0; // if ')' typed at the end of last field of function ... move // after closing character } else if (ch == FUNCTION_CLOSE_KEY && currentOffset == currentField.size() && parent instanceof MathFunction && parent.size() == currentField.getParentIndex() + 1) { currentOffset = parent.getParentIndex() + 1; currentField = (MathSequence) parent.getParent(); // if ')' typed at the end of last field of braces ... move // after closing character } else { if (ch == ',') { newCharacter(editorState, ch); // return so that the old current field and offset are not // set return; } } // topmost container last ... } else { // if ';' typed and at the top level ... insert delimiter char if (ch == DELIMITER_KEY || ch == ',') { newCharacter(editorState, ch); // return so that the old current field and offset are not set return; // update(); } } editorState.setCurrentField(currentField); editorState.setCurrentOffset(currentOffset); } private static void insertReverse(MathContainer parent, int parentIndex, ArrayList<MathComponent> removed) { for (int j = removed.size() - 1; j >= 0; j--) { MathComponent o = removed.get(j); int idx = parentIndex + (removed.size() - j); parent.addArgument(idx, o); } } private static ArrayList<MathComponent> cut(MathSequence currentField, int from, int to, EditorState st, MathComponent array, boolean rec) { int end = to < 0 ? currentField.size() - 1 : to; int start = from; if (st.getCurrentField() == currentField && st.getSelectionEnd() != null) { // the root is selected if (st.getSelectionEnd().getParent() == null && rec) { return cut((MathSequence) st.getSelectionEnd(), 0, -1, st, array, false); } // deep selection, e.g. a fraction if (st.getSelectionEnd().getParent() != currentField && rec) { return cut((MathSequence) st.getSelectionEnd().getParent(), st.getSelectionStart().getParentIndex(), st.getSelectionEnd().getParentIndex(), st, array, false); } // simple case: a part of sequence is selected end = currentField.indexOf(st.getSelectionEnd()); start = currentField.indexOf(st.getSelectionStart()); if (end < 0 || start < 0) { end = currentField.size() - 1; start = 0; } } ArrayList<MathComponent> removed = new ArrayList<MathComponent>(); for (int i = end; i >= start; i--) { removed.add(currentField.getArgument(i)); currentField.removeArgument(i); } currentField.addArgument(start, array); return removed; } private static ArrayList<MathComponent> cut(MathSequence currentField, int currentOffset) { ArrayList<MathComponent> removed = new ArrayList<MathComponent>(); for (int i = currentField.size() - 1; i >= currentOffset; i--) { removed.add(currentField.getArgument(i)); currentField.removeArgument(i); } return removed; } /** * Insert symbol. */ public void escSymbol(EditorState editorState) { editorState.getRootComponent().clearArguments(); editorState.setCurrentField(editorState.getRootComponent()); editorState.setCurrentOffset(0); editorState.resetSelection(); // String name = ArgumentHelper.readCharacters(editorState); // while (name.length() > 0) { // if (metaModel.isSymbol(name)) { // delCharacters(editorState, name.length()); // MetaCharacter meta = metaModel.getSymbol(name); // newCharacter(editorState, meta); // break; // // } else if (metaModel.isOperator(name)) { // delCharacters(editorState, name.length()); // MetaCharacter meta = metaModel.getOperator(name); // newCharacter(editorState, meta); // break; // // } else { // name = name.substring(1, name.length()); // } // } } /** * Backspace to remove container * * @param editorState * current state */ public void bkspContainer(EditorState editorState) { MathSequence currentField = editorState.getCurrentField(); // if parent is function (cursor is at the beginning of the field) if (currentField.getParent() instanceof MathFunction) { MathFunction parent = (MathFunction) currentField.getParent(); // fraction has operator like behavior if ("frac".equals(parent.getName())) { // if second operand is empty sequence if (currentField.getParentIndex() == 1 && currentField.size() == 0) { int size = parent.getArgument(0).size(); delContainer(editorState, parent, parent.getArgument(0)); // move after included characters editorState.addCurrentOffset(size); // if first operand is empty sequence } else if (currentField.getParentIndex() == 1 && parent.getArgument(0).size() == 0) { delContainer(editorState, parent, currentField); } } else if (metaModel.isGeneral(parent.getName())) { if (currentField.getParentIndex() == parent.getInsertIndex()) { delContainer(editorState, parent, currentField); } // not a fraction, and cursor is right after the sign } else { if (currentField.getParentIndex() == 0) { delContainer(editorState, parent, currentField); } } // if parent are empty array } else if (currentField.getParent() instanceof MathArray && currentField.getParent().size() == 1) { MathArray parent = (MathArray) currentField.getParent(); delContainer(editorState, parent, parent.getArgument(0)); // if parent is 1DArray or Vector and cursor is at the beginning of // intermediate the field } else if (currentField.getParent() instanceof MathArray && (((MathArray) currentField.getParent()).is1DArray() || ((MathArray) currentField.getParent()).isVector()) && currentField.getParentIndex() > 0) { int index = currentField.getParentIndex(); MathArray parent = (MathArray) currentField.getParent(); MathSequence field = parent.getArgument(index - 1); int size = field.size(); editorState.setCurrentOffset(0); while (currentField.size() > 0) { MathComponent component = currentField.getArgument(0); currentField.delArgument(0); field.addArgument(field.size(), component); } parent.delArgument(index); editorState.setCurrentField(field); editorState.setCurrentOffset(size); } // we stop here for now } public static void delContainer(EditorState editorState) { MathSequence currentField = editorState.getCurrentField(); // if parent is function (cursor is at the end of the field) if (currentField.getParent() instanceof MathFunction) { MathFunction parent = (MathFunction) currentField.getParent(); // fraction has operator like behavior if ("frac".equals(parent.getName())) { // first operand is current, second operand is empty sequence if (currentField.getParentIndex() == 0 && parent.getArgument(1).size() == 0) { int size = parent.getArgument(0).size(); delContainer(editorState, parent, currentField); // move after included characters editorState.addCurrentOffset(size); // first operand is current, and first operand is empty // sequence } else if (currentField.getParentIndex() == 0 && (currentField).size() == 0) { delContainer(editorState, parent, parent.getArgument(1)); } } // if parent are empty braces } else if (currentField.getParent() instanceof MathArray && currentField.getParent().size() == 1 && currentField.size() == 0) { MathArray parent = (MathArray) currentField.getParent(); int size = parent.getArgument(0).size(); delContainer(editorState, parent, parent.getArgument(0)); // move after included characters editorState.addCurrentOffset(size); // if parent is 1DArray or Vector and cursor is at the end of the // field } else if (currentField.getParent() instanceof MathArray && (((MathArray) currentField.getParent()).is1DArray() || ((MathArray) currentField.getParent()).isVector()) && currentField.getParentIndex() + 1 < currentField.getParent() .size()) { int index = currentField.getParentIndex(); MathArray parent = (MathArray) currentField.getParent(); MathSequence field = parent.getArgument(index + 1); int size = currentField.size(); while (currentField.size() > 0) { MathComponent component = currentField.getArgument(0); currentField.delArgument(0); field.addArgument(field.size(), component); } parent.delArgument(index); editorState.setCurrentField(field); editorState.setCurrentOffset(size); } // we stop here for now } public void bkspCharacter(EditorState editorState) { int currentOffset = editorState.getCurrentOffset(); if (currentOffset > 0) { if (editorState.getCurrentField() .getArgument(currentOffset - 1) instanceof MathArray) { MathArray parent = (MathArray) editorState.getCurrentField() .getArgument(currentOffset - 1); extendBrackets(parent, editorState); } else { editorState.getCurrentField().delArgument(currentOffset - 1); editorState.decCurrentOffset(); } } else { bkspContainer(editorState); } } private static void extendBrackets(MathArray array, EditorState editorState) { int currentOffset = array.getParentIndex() + 1; MathContainer currentField = array.getParent(); MathSequence lastArg = array.getArgument(array.size() - 1); int oldSize = lastArg.size(); while (currentField.size() > currentOffset) { MathComponent component = currentField.getArgument(currentOffset); currentField.delArgument(currentOffset); lastArg.addArgument(lastArg.size(), component); } editorState.setCurrentField(lastArg); editorState.setCurrentOffset(oldSize); } public void delCharacter(EditorState editorState) { int currentOffset = editorState.getCurrentOffset(); MathSequence currentField = editorState.getCurrentField(); if (currentOffset < currentField.size()) { CursorController.nextCharacter(editorState); bkspCharacter(editorState); } else { if (currentField.getParent() instanceof MathArray) { extendBrackets((MathArray) currentField.getParent(), editorState); } else { delContainer(editorState); } } } private static void delContainer(EditorState editorState, MathContainer container, MathSequence operand) { if (container.getParent() instanceof MathSequence) { // when parent is sequence MathSequence parent = (MathSequence) container.getParent(); int offset = container.getParentIndex(); // delete container parent.delArgument(container.getParentIndex()); // add content of operand while (operand.size() > 0) { MathComponent element = operand.getArgument(operand.size() - 1); operand.delArgument(operand.size() - 1); parent.addArgument(offset, element); } editorState.setCurrentField(parent); editorState.setCurrentOffset(offset); } } private static void delCharacters(EditorState editorState, int length0) { int currentOffset = editorState.getCurrentOffset(); MathSequence currentField = editorState.getCurrentField(); int length = length0; while (length > 0 && currentOffset > 0 && currentField .getArgument(currentOffset - 1) instanceof MathCharacter) { MathCharacter character = (MathCharacter) currentField .getArgument(currentOffset - 1); if (character.isOperator() || character.isSymbol()) { break; } currentField.delArgument(currentOffset - 1); currentOffset--; length--; } editorState.setCurrentOffset(currentOffset); } /** * remove characters before and after cursor * * @param editorState * @param lengthBeforeCursor * @param lengthAfterCursor */ public void removeCharacters(EditorState editorState, int lengthBeforeCursor, int lengthAfterCursor) { if (lengthBeforeCursor == 0 && lengthAfterCursor == 0) { return; // nothing to delete } MathSequence seq = editorState.getCurrentField(); for (int i = 0; i < lengthBeforeCursor; i++) { editorState.decCurrentOffset(); if (editorState.getCurrentOffset() < 0 || editorState.getCurrentOffset() >= seq.size()) { bkspContainer(editorState); return; } seq.delArgument(editorState.getCurrentOffset()); } for (int i = 0; i < lengthAfterCursor; i++) { seq.delArgument(editorState.getCurrentOffset()); } } /** * set ret to characters (no digit) around cursor * * @param ret * @return word length before cursor */ public static int getWordAroundCursor(EditorState editorState, StringBuilder ret) { int pos = editorState.getCurrentOffset(); MathSequence seq = editorState.getCurrentField(); StringBuilder before = new StringBuilder(); int i; for (i = pos - 1; i >= 0; i--) { try { before.append(getLetter(seq.getArgument(i))); } catch (Exception e) { break; } } int lengthBefore = pos - i - 1; StringBuilder after = new StringBuilder(); for (i = pos; i < seq.size(); i++) { try { after.append(getLetter(seq.getArgument(i))); } catch (Exception e) { break; } } before.reverse(); ret.append(before); ret.append(after); return lengthBefore; } public static boolean deleteSelection(EditorState editorState) { boolean nonempty = false; if (editorState.getSelectionStart() != null) { MathContainer parent = editorState.getSelectionStart().getParent(); int end, start; if (parent == null) { // all the formula is selected parent = editorState.getRootComponent(); start = 0; end = parent.size() - 1; } else { end = parent.indexOf(editorState.getSelectionEnd()); start = parent.indexOf(editorState.getSelectionStart()); } if (end >= 0 && start >= 0) { for (int i = end; i >= start; i--) { parent.delArgument(i); nonempty = true; } editorState.setCurrentOffset(start); // in most cases no impact; goes to parent node when whole // formula selected if (parent instanceof MathSequence) { editorState.setCurrentField((MathSequence) parent); } } } editorState.resetSelection(); return nonempty; } /** * @param editorState * current state * @param ch * single char * @return whether it was handled */ public boolean handleChar(EditorState editorState, char ch) { boolean handled = false; boolean allowFrac = createFrac && !editorState.isInsideQuotes(); // backspace, delete and escape are handled for key down if (ch == KeyEvent.VK_BACK_SPACE || ch == KeyEvent.VK_DELETE || ch == KeyEvent.VK_ESCAPE) { return true; } if (ch != '(' && ch != '{' && ch != '[' && ch != '/' && ch != '|' && ch != MetaModelArrays.LFLOOR && ch != MetaModelArrays.LCEIL) { deleteSelection(editorState); } MetaModel meta = editorState.getMetaModel(); if (meta.isArrayCloseKey(ch)) { endField(editorState, ch); handled = true; } else if (meta.isFunctionOpenKey(ch)) { newBraces(editorState, ch); handled = true; } else if (allowFrac && ch == '^') { newScript(editorState, "^"); handled = true; } else if (allowFrac && ch == '_') { newScript(editorState, "_"); handled = true; } else if (allowFrac && ch == '/') { // slash used in android ggb // keyboard newFunction(editorState, "frac", 1); handled = true; } else if (allowFrac && ch == 47) { // simple / char newFunction(editorState, "frac", 1); handled = true; } else if (ch == 8730) { // square root char newFunction(editorState, "sqrt", 0); handled = true; } else if (meta.isArrayOpenKey(ch)) { newArray(editorState, 1, ch); handled = true; } else if (ch == 8226) { // big dot char newOperator(editorState, '*'); handled = true; } else if (ch == 215) { // multiplication cross char newOperator(editorState, '*'); handled = true; } else if (ch == ',' && allowFrac) { // multiplication cross char comma(editorState); handled = true; } else if (meta.isOperator("" + ch)) { newOperator(editorState, ch); handled = true; } else if (meta.isSymbol("" + ch)) { newSymbol(editorState, ch); handled = true; } else if (meta.isCharacter("" + ch)) { newCharacter(editorState, ch); handled = true; } return handled; } private void comma(EditorState editorState) { if (trySelectNext(editorState)) { return; } newOperator(editorState, ','); } public static boolean trySelectNext(EditorState editorState) { int idx = editorState.getCurrentOffset(); if (editorState.getSelectionEnd() != null) { idx = editorState.getSelectionEnd().getParentIndex() + 1; } MathSequence field = editorState.getCurrentField(); if (field.getArgument(idx) instanceof MathCharacter && ",".equals(field.getArgument(idx).toString()) && doSelectNext(field, editorState, idx + 1)) { return true; } return false; } public static boolean trySelectFirst(EditorState editorState) { int idx = editorState.getCurrentOffset(); if (editorState.getSelectionEnd() != null) { idx = editorState.getSelectionEnd().getParentIndex() + 1; } MathSequence field = editorState.getCurrentField(); if (idx == field.size() - 1 && doSelectNext(field, editorState, 0)) { return true; } return false; } /** * @param args * text of the form <arg1>,<arg2> * @param state * current state * @param offset * where to start looking * @return whether successfully selected */ public static boolean doSelectNext(MathSequence args, EditorState state, int offset) { int endchar = -1; for (int i = offset + 1; i < args.size(); i++) { if (args.getArgument(i) instanceof MathCharacter && ((MathCharacter) args.getArgument(i)) .getUnicode() == '>') { endchar = i; break; } } if (endchar > 0) { state.setCurrentField(args); state.setSelectionStart(args.getArgument(offset)); state.setSelectionEnd(args.getArgument(endchar)); state.setCurrentOffset(endchar); return true; } return false; } public void paste() { if (mathField != null) { mathField.paste(); } } public void copy() { if (mathField != null) { mathField.copy(); } } public static MathSequence getSelectionText(EditorState editorState) { if (editorState.getSelectionStart() != null) { MathContainer parent = editorState.getSelectionStart().getParent(); int end, start; if (parent == null) { // all the formula is selected return editorState.getRootComponent(); } MathSequence seq = new MathSequence(); end = parent.indexOf(editorState.getSelectionEnd()); start = parent.indexOf(editorState.getSelectionStart()); if (end >= 0 && start >= 0) { for (int i = start; i <= end; i++) { seq.addArgument(parent.getArgument(i).copy()); } // editorState.setCurrentOffset(start); } return seq; } // editorState.resetSelection(); return null; } }