package com.llamacorp.equate; import org.json.JSONException; import org.json.JSONObject; import java.math.BigDecimal; import java.math.MathContext; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Collections; public class Expression { private static final String JSON_EXPRESSION = "expression"; private static final String JSON_PRECISE = "precise"; private static final String JSON_START = "sel_start"; private static final String JSON_END = "sel_end"; private static final String JSON_SOLVED = "sel_end"; public enum NumFormat {NORMAL, PLAIN, SCI_NOTE, ENGINEERING} //the main expression string private String mExpression; //this string stores the more precise result after solving private String mPreciseResult; private MathContext mMCDisplay; private int mIntDisplayPrecision; //highlighted text selection private int mSelectionStart; private int mSelectionEnd; //stores whether or not this expression was just solved private boolean mSolved; private NumFormat mNumFormat = NumFormat.NORMAL; //list of indexes of characters to highlight private ArrayList<Integer> mHighlightedCharList; public static final String regexDecimal = "\\."; public static final String regexGroupedExponent = "(\\^)"; public static final String regexGroupedMultiDiv = "([/*])"; public static final String regexGroupedAddSub = "([+-])"; //note that in []'s only ^, -, and ] need escapes. - doesn't need one if invalid public static final String regexNonNegOperators = "+/*^%"; private static final String regexOperators = regexNonNegOperators + "-"; private static final String regexInvalidChars = "[^0-9()E." + regexOperators + "]"; private static final String regexHasInvalidChars = ".*" + regexInvalidChars + ".*"; private static final String regexInvalidStartChar = "[E" + regexNonNegOperators + "]"; private static final String regexAnyValidOperator = "[" + regexOperators + "]"; private static final String regexAnyOpExceptPercent = regexAnyValidOperator.replace("%", ""); private static final String regexAnyOperatorOrE = "[E" + regexOperators + "]"; public static final String regexGroupedNumber = "([-]?\\d*[.]?\\d+[.]?(?:E[+-]?\\d+)?)"; public static final String regexGroupedNonNegNumber = "((?:(?<=^)[-])?(?:(?<=[*+(/-])[-])?\\d*[.]?\\d+[.]?(?:E[+-]?\\d+)?)"; //this isn't really needed anymore, if want non capturing group, use ?:" public static final int numGroupsInRegexGroupedNumber = 1; //the following is a more concise version, but doesn't work with NP++ due to the | in the lookbehind //public static final String regexGroupedNonNegNumber = "((?:(?<=^|[*+/])\\-)?\\d*\\.?\\d+\\.?(?:E[\\-\\+]?\\d+)?)"; private String[][] substituteChars = new String[][]{{"[\u00f7\u00B7]", "/"}, //alt-246 {"[x\u00d7]", "*"},//alt-0215,249,250 {"[\u0096\u0097]", "-"}}; //alt-0151,0150 //TODO add in all these characters public Expression(int displayPrecision) { clearExpression(); mPreciseResult = ""; setSelection(0, 0); mHighlightedCharList = new ArrayList<>(); clearHighlightedList(); //skip precise unit usage if precision is set to 0 if (displayPrecision > 0){ mIntDisplayPrecision = displayPrecision; mMCDisplay = new MathContext(mIntDisplayPrecision); } } public Expression(JSONObject json, int displayPrecision) throws JSONException { this(displayPrecision); replaceExpression(json.getString(JSON_EXPRESSION)); mPreciseResult = json.getString(JSON_PRECISE); //TODO this is breaking // mSelectionStart = json.getInt(JSON_START); // mSelectionEnd = json.getInt(JSON_END); setSolved(json.getBoolean(JSON_SOLVED)); } /** * Used to create a copy of expression. * * @param exp template to use as copy */ public Expression(Expression exp) { replaceExpression(exp.toString()); mPreciseResult = exp.getPreciseResult(); mIntDisplayPrecision = exp.mIntDisplayPrecision; mMCDisplay = exp.mMCDisplay; mSelectionEnd = exp.getSelectionEnd(); mSelectionStart = exp.getSelectionStart(); mSolved = exp.isSolved(); mNumFormat = exp.mNumFormat; mHighlightedCharList = exp.getHighlighted(); } public JSONObject toJSON() throws JSONException { JSONObject json = new JSONObject(); json.put(JSON_EXPRESSION, toString()); json.put(JSON_PRECISE, mPreciseResult); json.put(JSON_START, getSelectionStart()); json.put(JSON_END, getSelectionEnd()); json.put(JSON_SOLVED, isSolved()); return json; } /** * This function will try to add a number or operator, or entire result list * to the current expression. Note that there is lots of error checking to * sure user can't entire an invalid operator/number * * @param sKey should only be single valid number or operator character, * or longer previous results * @return returns if a solve should be performed */ public boolean keyPresses(String sKey) { //for now, if we're adding a previous result, just add it without error checking if (sKey.length() > 1){ //don't load in errors if (isInvalid(sKey)) return false; if (isSolved()) replaceExpression(sKey); else insertAtSelection(sKey); setSolved(false); return false; } //if we have negation, instantly perform and return if (sKey.equals("n")){ negateLastNumber(); return false; } //if we have inversion, instantly perform and return if (sKey.equals("i")){ int[] toHighlight = invertLastNumber(); //highlight the newly added invert symbols if not immediately solving if (!isSolved()) markHighlighted(toHighlight); return isSolved(); } //add auto completion for close parentheses if (sKey.equals("]")){ if (numOpenPara() <= 0){ //if more close than open, add an open insertAt("(", 0); } sKey = ")"; } if (sKey.equals("[")){ insertAt(")", length()); sKey = "("; } //if just hit equals, and we hit [.0-9(], then clear expression if (isSolved() && sKey.matches("[.0-9(]")) clearExpression(); if (!isEntryValid(sKey)) return false; //add implicit * before ( if previous character was #, decimal, or ) if (sKey.equals("(") && expressionToSelection().matches(".*[\\d).]$")){ sKey = "*" + sKey; markHighlighted(expressionToSelection().length()); } //when adding # after ), add multiply if (sKey.matches("[.0-9]") && expressionToSelection().matches(".*[)]$")){ sKey = "*" + sKey; markHighlighted(expressionToSelection().length()); } //if we have "84*-", replace both the * and the - with the operator if (sKey.matches(regexAnyValidOperator) && expressionToSelection().matches(".*" + regexAnyOpExceptPercent + regexAnyValidOperator + "$")){ //if we have something highlighted, delete it first if (getSelectionEnd() > getSelectionStart()) backspaceAtSelection(); backspaceAtSelection(); backspaceAtSelection(); } //if there's already an operator, replace it with the new operator, except for -, let that stack up else if (sKey.matches(regexInvalidStartChar) && expressionToSelection().matches(".*" + regexAnyOpExceptPercent + "$")){ //if * is already there, swap operator fo ^ if (sKey.equals("*") && expressionToSelection().matches(".*" + "[*]" + "$")) sKey = "^"; //if we have something highlighted, delete it first if (getSelectionEnd() > getSelectionStart()) backspaceAtSelection(); backspaceAtSelection(); } //otherwise load the new keypress insertAtSelection(sKey); //try to highlight matching set of para if we just closed or opened one highlightMatchingPara(sKey); //we added a num/op, reset the solved flag setSolved(false); return false; } private boolean isEntryValid(String sKey) { //check for invalid entries if (sKey.matches(regexHasInvalidChars)) throw new IllegalArgumentException("In addToExpression, invalid sKey..."); //don't start with [*/^E] when the expression string is empty or if we opened a para if (sKey.matches(regexInvalidStartChar) && expressionToSelection().matches("(^[-]?$)|(.*[(]$)")) return false; //if we already have a decimal or E in the number, don't add a decimal if (sKey.equals(".") && getLastPartialNumb().matches(".*[.E].*")) //lastNumb returns the last num; if expression="4.3+", returns "4.3"; if last key was an operator, allow decimals if (!expressionToSelection().matches(".*" + regexAnyValidOperator + "$")) return false; //if we already have a E in the number, don't add another; also don't add E immediately after an operator if (sKey.equals("E") && (getLastPartialNumb().contains("E") || expressionToSelection().matches(".*" + regexAnyValidOperator + "$"))) return false; //if "E" or "E-" was last pressed, only allow [0-9(-] if (getLastPartialNumb().matches(".*E[-]?$")) if (sKey.matches("[^\\d(-]")) return false; //if last digit was only a decimal, don't add any operator or E if (sKey.matches(regexAnyOperatorOrE) && getLastPartialNumb().equals(".")) return false; //don't allow "--" or "65E--" if (sKey.matches("[-]") && expressionToSelection().matches(".*E?[-]")) return false; //don't allow two %'s in a row, or "5%*" then another "%" if (sKey.matches("%") && expressionToSelection().matches(".*%" + regexAnyValidOperator + "?$")) return false; //no problems, return valid entry return true; } /** * This function takes text pasted by user, formats it and loads it into the expression * * @param str text to clean and load into expression */ public void pasteIntoExpression(String str) { //first replace all substitutable characters for (String[] substituteChar : substituteChars) str = str.replaceAll(substituteChar[0], substituteChar[1]); //next remove all invalid chars str = str.replaceAll(regexInvalidChars, ""); //then just blindly insert text without case checking insertAtSelection(str); //likely not necessary, since the click on EditText should've overwritten solved setSolved(false); } /** * Rounds expression down by a MathContext mcDisp, formats it in sci notation * or not, and replaces the current expression with the result * * @throws NumberFormatException if Expression not formatted correctly */ public void roundAndCleanExpression(NumFormat numFormat) throws NumberFormatException { //if expression was displaying error (with invalid chars) leave if (isInvalid() || isEmpty()) return; //save current numformat (used to tell if we want to show eng prefix) setFormat(numFormat); //if formatting messed ("-", "(())"), or number too big, throw error BigDecimal bd = new BigDecimal(getExpression(), mMCDisplay); //only after getExpression() was successfully converted to BigDecimal //save the original to precise result for potential later use mPreciseResult = getExpression(); String formatStr; switch (numFormat) { case NORMAL: //determine if exponent (number after E) is small enough for non-engineering style print, otherwise do regular style if (lastNumbExponent() < mIntDisplayPrecision) formatStr = bd.toPlainString(); else formatStr = bd.toString(); break; case PLAIN: formatStr = bd.toPlainString(); break; case SCI_NOTE: formatStr = getSciNotation(bd, mIntDisplayPrecision, false); break; case ENGINEERING: formatStr = getSciNotation(bd, mIntDisplayPrecision, true); break; default: throw new NumberFormatException("Invalid number format"); } //finally clean the result off and set it in mExpression replaceExpression(cleanFormatting(formatStr)); } /** * Takes a BigDecimal and returns a string formatted in scientific notation. * Has option of returning string in engineering scientific notation, where * the exponent is always a multiple of 3 (eg 53E3 or 360E-9) * * @param bd is the number to convert into a string in sci notation * @param digits number of digits of precision (eg 5 for 1.2345E8) * @param engStr flag for regular scientific notation, or engineering style. * @return String representation of the number in scientific notation */ private static String getSciNotation(BigDecimal bd, int digits, boolean engStr) { String format = engStr ? "##0.0E0" : "#.0E0"; DecimalFormat formatter = new DecimalFormat(format); //formatter.setRoundingMode(RoundingMode.HALF_UP); formatter.setMinimumFractionDigits(digits); return formatter.format(bd); } /** * Adds parenthesis around power operations */ public static String groupPowerOperands(String str) { //search to find "^" (start at 1 so no NPE later; "^3" invalid anyway) for (int i = 1; i < str.length(); i++) { if (str.charAt(i) == '^'){ int openPareIndex = 0; int closePareIndex = 0; //first find where to add ( //first case is (###)^ if (str.charAt(i - 1) == ')'){ for (int k = i - 2; k > 0; k--) { if (numOpenPara(str.substring(k, i)) == 0){ openPareIndex = k; break; } } } //second case is just #^ else { String lastNumb = getLastNumb(str.substring(0, i)); //make nonneg (^ is beginning of, not power operator) lastNumb = lastNumb.replaceAll("^\\-", ""); openPareIndex = i - lastNumb.length(); } //next find where to add ) //first case is ^(###) if (str.charAt(i + 1) == '('){ for (int k = i + 2; k <= str.length(); k++) { if (numOpenPara(str.substring(i, k)) == 0){ closePareIndex = k; break; } } } //second case is just #^ else { String firstNum = getFirstNumb(str.substring(i + 1, str.length())); closePareIndex = i + 1 + firstNum.length(); } //actually add in pares str = str.substring(0, openPareIndex) + "(" + str.substring(openPareIndex, closePareIndex) + ")" + str.substring(closePareIndex, str.length()); //advanced index beyond ^#) i = closePareIndex; } } return str; } /** * Replaces % operators their respective operators */ public static String replacePercentOps(String str) { String subStr; String strAfter; for (int i = 0; i < str.length(); i++) { if (str.charAt(i) == '%'){ //save beginning portion of string before the % subStr = str.substring(0, i); //get the number that was right before the % String lastNum = getLastNumb(subStr); //trim off the last number subStr = subStr.substring(0, subStr.length() - lastNum.length()); //as long as the string isn't empty, find last operator strAfter = str.substring(i + 1, str.length()); if (!subStr.equals("")){ String lastOp = subStr.substring(subStr.length() - 1, subStr.length()); //trim off the last operator subStr = subStr.substring(0, subStr.length() - 1); //case similar to 2+5% or 2-5%, but no 2+5%3 or 2+5%*3 if (lastOp.matches(regexGroupedAddSub) && !subStr.equals("") && (strAfter.equals("") || strAfter.matches(regexGroupedAddSub + ".*$"))) subStr = "(" + subStr + ")*(1" + lastOp + lastNum + "*0.01)"; //cases like 2*3% or 3% or similar else subStr = subStr + lastOp + "(" + lastNum + "*0.01)"; } else subStr = "(" + lastNum + "*0.01)"; //replace the old contents with the new expression str = subStr + strAfter; //move up i by the diff between the new and old str i = subStr.length(); } } return str; } /** * Adds implied multiples for parenthesis */ public static String addImpliedParMult(String str) { //first replace all )( with )*( str = str.replaceAll("\\)\\(", "\\)\\*\\("); //replace all #( with #*( str = str.replaceAll("([\\d\\.])\\(", "$1\\*\\("); //replace all )# with )*# str = str.replaceAll("\\)([\\d\\.])", "\\)\\*$1"); return str; } /** * Searches forward in string for associated close parenthesis given the * index of an open * * @param str String to look search over * @param firstOpenIndex Index in str of the open parenthesis requiring a mate * @return The index of the associated close parenthesis give the supplied open * returns -1 if no such associated parenthesis was found */ public static int findMatchingClosePara(String str, int firstOpenIndex) { if (!str.equals("") && firstOpenIndex != -1){ int paraCount = 0; for (int i = firstOpenIndex; i < str.length(); i++) { if (str.charAt(i) == '(') paraCount++; else if (str.charAt(i) == ')'){ paraCount--; if (paraCount == 0){ return i; } } } } return -1; } /** * Searches forward in string for associated close parenthesis given the * index of an open * * @param str String to look search over * @param lastCloseIndex Index in str of the close parenthesis requiring a mate * @return The index of the associated open parenthesis give the supplied close * returns -1 if no such associated parenthesis was found */ public static int findMatchingOpenPara(String str, int lastCloseIndex) { if (!str.equals("") && lastCloseIndex != -1){ int paraCount = 0; for (int i = lastCloseIndex; i >= 0; i--) { if (str.charAt(i) == ')') paraCount++; else if (str.charAt(i) == '('){ paraCount--; if (paraCount == 0){ return i; } } } } return -1; } /** * Close any open parentheses in this expression */ public void closeOpenPar() { //if more open parentheses then close, add corresponding close para's int numCloseParaToAdd = numOpenPara(); for (int i = 0; i < numCloseParaToAdd; i++) { setExpression(getExpression() + ")"); } } /** * Load in more precise result if possible */ public void loadPreciseResult() { //make sure we have valid precise result and rounding Mathcontext first if (mPreciseResult.equals("") || mMCDisplay == null) return; //make the precise string not precise temporarily for comparison BigDecimal formallyPrec = new BigDecimal(mPreciseResult, mMCDisplay); String formallyPrecCleaned = cleanFormatting(formallyPrec.toString()); //find out if expression's first term matches first part of the precise result, if so replace with more precise term if (getFirstNumb().equals(formallyPrecCleaned)){ replaceExpression(getExpression().replaceFirst(regexGroupedNumber, mPreciseResult)); } } /** * Clean off any dangling operators and E's (not parentheses!!) at the END ONLY */ public void cleanDanglingOps() { //don't want to trim off %'s so long as there's at least one char before it if (getExpression().matches(".+%$")) return; replaceExpression(getExpression().replaceAll(regexAnyOperatorOrE + "+$", "")); // if result is just ".", remove it replaceExpression(getExpression().replaceAll("^" + regexDecimal + "$", "")); } public void setSelection(int selectionStart, int selectionEnd) { if (selectionEnd > length() || selectionStart > length()) throw new IllegalArgumentException("In Expression.setSelection, selection end or start > expression length"); //this occurs if the use drags the end selector before the start selector; need the following code so expression doesn't get confused if (selectionEnd < selectionStart){ int temp = selectionEnd; selectionEnd = selectionStart; selectionStart = temp; } mSelectionStart = selectionStart; mSelectionEnd = selectionEnd; } /** * Clears entire expression. Note selection will move to 0,0 */ public void clearExpression() { replaceExpression(""); setSolved(false); } /** * Replaces entire expression. Selection moves to end of the expression. * * @param tempExp String to replace expression with */ public void replaceExpression(String tempExp) { setExpression(tempExp); setSelectionToEnd(); } public void backspaceAtSelection() { int selStart = getSelectionStart(); int selEnd = getSelectionEnd(); //return if nothing to delete or selection at beginning of expression if (isEmpty() || selEnd == 0) return; //if something is highlighted, delete highlighted part (replace with "") if (selStart != selEnd) insertAtSelection(""); else { setExpression(getExpression().substring(0, selStart - 1) + expressionAfterSelectionStart()); setSelection(selStart - 1, selStart - 1); } } /** * Returns if this expression is empty */ public boolean isEmpty() { return isEmpty(getExpression()); } /** * determine if we are are in sci notation already */ public boolean isSciNotation() { return toString().matches(".*E.*"); } /** * Returns if input parameter expression is empty */ public static boolean isEmpty(String str) { return str.equals(""); } /** * Returns if this expression is has invalid characters */ public static boolean isInvalid(String str) { return str.matches(regexHasInvalidChars); } /** * Returns if this expression is has invalid characters */ public boolean isInvalid() { return isInvalid(getExpression()); } /** * Returns if expression just contains a valid number */ public boolean isOnlyValidNumber() { return !(isInvalid() || isEmpty() || containsOps() || containsParens()); } /** * Tell whether or not this expression contains operators * * @return false if empty or only contains a number ("", "3E1", or "-4"), * true for a result such as "2-3" "2^4" etc */ public boolean containsOps() { return getLastNumb(getExpression()).length() != length(); } /** * Returns true if this expression contains either open or close parenthesis * such as "(53" or "((1))" */ public boolean containsParens() { return getExpression().matches(".*[()].*"); } /** * Returns the post rounded result */ public String getPreciseResult() { return mPreciseResult; } /** * @return if there are characters marked for highlighting * in the current expression */ public boolean isHighlighted() { return mHighlightedCharList.size() != 0; } public ArrayList<Integer> getHighlighted() { return mHighlightedCharList; } public void clearHighlightedList() { mHighlightedCharList.clear(); } public int getSelectionStart() { return mSelectionStart; } public int getSelectionEnd() { return mSelectionEnd; } public void setSelectionToEnd() { setSelection(length(), length()); } public NumFormat getNumFormat() { return mNumFormat; } public void setFormat(NumFormat numFormat) { this.mNumFormat = numFormat; } public boolean isSolved() { return mSolved; } public void setSolved(boolean solved) { mSolved = solved; } /** * Returns the length of the current expression * * @return length of expression */ public int length() { return getExpression().length(); } /** * Returns the current expression in expressed as a String */ @Override public String toString() { return getExpression(); } /** * Clean up a string's formatting * * @param sToClean is the string that will be cleaned */ static private String cleanFormatting(String sToClean) { //clean off any dangling .'s and .0's sToClean = sToClean.replaceAll("\\.0*$", ""); //clean off 0's after decimal sToClean = sToClean.replaceAll("(\\.\\d*[1-9])0+$", "$1"); //remove +'s from #E+# sToClean = sToClean.replaceAll("E\\+", "E"); //remove 0's before E ei 6.1000E4 to 6.1E4; or 6.000E4 to 6.1E4; but leave // 0E8 and 100E4 as themselves sToClean = sToClean.replaceAll("(\\.\\d*?)0+E", "$1E"); //remove those pesky decimal points before an E (like 1.E8) sToClean = sToClean.replaceAll("\\.E", "E"); return sToClean; } /** * This function will negate the last number before the selection * If expression is empty, add a minus; */ private void negateLastNumber() { String str = expressionToSelection(); String lastNum = getLastNumb(str); int frontLen = str.length() - lastNum.length(); String endStr = str.substring(0, frontLen); //if we had 5+-6, remove the - before the 6 if (lastNum.matches("[-].*")){ endStr = endStr + lastNum.substring(1, lastNum.length()); } //add in a minus and the shorten unnecessary signs else { endStr = endStr + "-" + lastNum; endStr = endStr.replace("+-", "-").replace("--", "+"); markHighlighted(endStr.length() - 1 - lastNum.length()); } setExpression(endStr + expressionAfterSelectionStart()); setSelection(endStr.length(), endStr.length()); } /** * This function will add a "1/(" before the last number before the selection * If the expression is solved, also solve again * * @return an array of indexes that were added */ private int[] invertLastNumber() { String str = expressionToSelection(); String lastNum = getLastNumb(str); int frontLen = str.length() - lastNum.length(); String inv = "1/("; insertAt(inv, frontLen); frontLen = frontLen + inv.length(); String expEnd = getExpression().substring(frontLen, length()); int lenFirstNum = getFirstNumb(expEnd).length(); insertAt(")", frontLen + lenFirstNum); //move cursor back into the parenthesis if no number was inverted if (lenFirstNum == 0) setSelection(getSelectionStart() - 1, getSelectionEnd() - 1); //mark indexes of elements added return new int[]{frontLen - 3, frontLen - 2, frontLen - 1, frontLen + lenFirstNum}; } /** * Counts the number of open vs. number of closed parentheses in expressionToSelection() * * @return 0 if equal num of open/close para, positive # if more open, neg # if more close */ private int numOpenPara() { return numOpenPara(expressionToSelection()); } /** * Counts the number of open vs. number of closed parentheses in the given string * * @param str is String containing parenthesis to count * @return 0 if equal num of open/close para, positive # if more open, neg # if more close */ private static int numOpenPara(String str) { int numOpen = 0; int numClose = 0; for (int i = 0; i < str.length(); i++) { if (str.charAt(i) == '(') numOpen++; if (str.charAt(i) == ')') numClose++; } return numOpen - numClose; } private void highlightMatchingPara(String sKey) { String open = "("; String close = ")"; int associatedIndex; if (sKey.equals(close)){ //search for backwards for the matching open associatedIndex = findMatchingOpenPara(expressionToSelection(), expressionToSelection().lastIndexOf(close)); } else if (sKey.equals(open) || sKey.equals("*" + open)){ //search for forwards from selection for the matching close associatedIndex = findMatchingClosePara(getExpression(), getSelectionStart() - 1); } else return; if (associatedIndex != -1){ int[] tmpArray = {getSelectionStart() - 1, associatedIndex}; markHighlighted(tmpArray); } } /** * Mark one character in the expression for highlighting * during the next screen update. * * @param index is 0 indexed, so in "74" to highlight 7, pass 0 */ private void markHighlighted(int index) { int[] tmpArray = {index}; markHighlighted(tmpArray); } /** * Mark two characters in the expression for highlighting * during the next screen update. * * @param indexArray is 0 indexed, so in "74" to highlight 7, pass 0 */ private void markHighlighted(int[] indexArray) { clearHighlightedList(); for (int index : indexArray) { mHighlightedCharList.add(index); } Collections.sort(mHighlightedCharList); } private void setExpression(String tempExp) { mExpression = tempExp; } private String getExpression() { return mExpression; } private String expressionToSelection() { return getExpression().substring(0, getSelectionStart()); } private String expressionAfterSelectionStart() { return getExpression().substring(getSelectionStart(), length()); } /** * Add a String to this expression at the correct selection point * Note if something is highlighted (selStart != selEnd), this * selection will be replaced * * @param toAdd the String to add */ private void insertAtSelection(String toAdd) { //delete the current highlighted selection (if it exists) if (getSelectionStart() != getSelectionEnd()){ setExpression(getExpression().substring(0, getSelectionStart()) + getExpression().substring(getSelectionEnd(), length())); //update the selections to reflected deleted highlighted selection setSelection(getSelectionStart(), getSelectionStart()); } insertAt(toAdd, getSelectionStart()); } /** * Inserts a string at the insert location and preserves the user's selection * * @param toAdd the string to insert * @param insertLocation the location to insert the string. In "42+3", 0 would * specify before the 4, 1 would be before the 2, etc. */ private void insertAt(String toAdd, int insertLocation) { //return if we're trying to insert at invalid location if (insertLocation > length() | insertLocation < 0) return; //actually insert text into the expression setExpression(getExpression().substring(0, insertLocation) + toAdd + getExpression().substring(insertLocation, length())); //move up the selection start if necessary int selStart = getSelectionStart(); int selEnd = getSelectionEnd(); if (selStart == selEnd && insertLocation <= selStart){ selStart = selStart + toAdd.length(); selEnd = selStart; } else { if (insertLocation <= selStart) selStart = selStart + toAdd.length(); if (insertLocation < selEnd) selEnd = selEnd + toAdd.length(); } setSelection(selStart, selEnd); } /** * Gets the number after the E in expression (not including + and -) */ private int lastNumbExponent() { //func returns "" if expression empty, and expression if doesn't contain E[+-]? if (getExpression().contains("E")){ String[] strA = getExpression().split("E[+-]?"); return Integer.parseInt(strA[strA.length - 1]); } else //need to be bigger than DISPLAY_PRECISION so calling func uses toString instead of toPlainString return mIntDisplayPrecision + 2; } /** * Gets the first number (returned as a String) of string * * @param str is String in which to find first number * @return anything before the first valid operator, or "" if expression empty, * or entire expression if doesn't contain regexAnyValidOperator (for "-3E-4-5" returns "-3E-4") */ private static String getFirstNumb(String str) { String[] strA = str.split("(?<!^|E)" + regexAnyValidOperator); return strA[0]; } /** * Gets the first number (returned as a String) at selection in current expression * * @return anything before the first valid operator, or "" if expression empty, * or entire expression if doesn't contain regexAnyValidOperator */ private String getFirstNumb() { return getFirstNumb(expressionToSelection()); } /** * Gets the last partial number (returned as a String) at selection in current expression * * @return last partial number, invalid or not, or "" if expression empty, or entire expression * if doesn't contain regexAnyValidOperator. Note if expression is "1+-5*" it will return "-5" * This number might be valid (eg "1E3") or invalid, (eg "1E", "1.E-" or "34)") */ private String getLastPartialNumb() { String[] strA = expressionToSelection().split("(?<!^|[E*^/%+])" + regexAnyValidOperator); if (strA.length == 0) return ""; else return strA[strA.length - 1]; } /** * Gets the last number (returned as a String) for input string * * @param str is String expression to find last number of * @return Last valid number in expression, "" If expression empty, or entire * expression if doesn't contain regexAnyValidOperator. For expStr = "1+-5", * return "-5". If expStr = "1-5", return "5"; for "(-45", return "-45", for * "(53)" return "" */ private static String getLastNumb(String str) { if (str.matches(".*" + regexGroupedNonNegNumber)) return str.replaceAll(".*?" + regexGroupedNonNegNumber + "$", "$1"); return ""; } }