/*
|
* Copyright (C) 2015 The Android Open Source Project
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License.
|
*/
|
|
package com.android.calculator2;
|
|
import android.content.res.Resources;
|
import android.content.Context;
|
import android.app.Activity;
|
import android.util.Log;
|
import android.view.View;
|
import android.widget.Button;
|
|
import java.text.DecimalFormatSymbols;
|
import java.util.HashMap;
|
import java.util.Locale;
|
|
/**
|
* Collection of mapping functions between key ids, characters, internationalized
|
* and non-internationalized characters, etc.
|
* <p>
|
* KeyMap instances are not meaningful; everything here is static.
|
* All functions are either pure, or are assumed to be called only from a single UI thread.
|
*/
|
public class KeyMaps {
|
/**
|
* Map key id to corresponding (internationalized) display string.
|
* Pure function.
|
*/
|
public static String toString(Context context, int id) {
|
switch(id) {
|
case R.id.const_pi:
|
return context.getString(R.string.const_pi);
|
case R.id.const_e:
|
return context.getString(R.string.const_e);
|
case R.id.op_sqrt:
|
return context.getString(R.string.op_sqrt);
|
case R.id.op_fact:
|
return context.getString(R.string.op_fact);
|
case R.id.op_pct:
|
return context.getString(R.string.op_pct);
|
case R.id.fun_sin:
|
return context.getString(R.string.fun_sin) + context.getString(R.string.lparen);
|
case R.id.fun_cos:
|
return context.getString(R.string.fun_cos) + context.getString(R.string.lparen);
|
case R.id.fun_tan:
|
return context.getString(R.string.fun_tan) + context.getString(R.string.lparen);
|
case R.id.fun_arcsin:
|
return context.getString(R.string.fun_arcsin) + context.getString(R.string.lparen);
|
case R.id.fun_arccos:
|
return context.getString(R.string.fun_arccos) + context.getString(R.string.lparen);
|
case R.id.fun_arctan:
|
return context.getString(R.string.fun_arctan) + context.getString(R.string.lparen);
|
case R.id.fun_ln:
|
return context.getString(R.string.fun_ln) + context.getString(R.string.lparen);
|
case R.id.fun_log:
|
return context.getString(R.string.fun_log) + context.getString(R.string.lparen);
|
case R.id.fun_exp:
|
// Button label doesn't work.
|
return context.getString(R.string.exponential) + context.getString(R.string.lparen);
|
case R.id.lparen:
|
return context.getString(R.string.lparen);
|
case R.id.rparen:
|
return context.getString(R.string.rparen);
|
case R.id.op_pow:
|
return context.getString(R.string.op_pow);
|
case R.id.op_mul:
|
return context.getString(R.string.op_mul);
|
case R.id.op_div:
|
return context.getString(R.string.op_div);
|
case R.id.op_add:
|
return context.getString(R.string.op_add);
|
case R.id.op_sub:
|
return context.getString(R.string.op_sub);
|
case R.id.op_sqr:
|
// Button label doesn't work.
|
return context.getString(R.string.squared);
|
case R.id.dec_point:
|
return context.getString(R.string.dec_point);
|
case R.id.digit_0:
|
return context.getString(R.string.digit_0);
|
case R.id.digit_1:
|
return context.getString(R.string.digit_1);
|
case R.id.digit_2:
|
return context.getString(R.string.digit_2);
|
case R.id.digit_3:
|
return context.getString(R.string.digit_3);
|
case R.id.digit_4:
|
return context.getString(R.string.digit_4);
|
case R.id.digit_5:
|
return context.getString(R.string.digit_5);
|
case R.id.digit_6:
|
return context.getString(R.string.digit_6);
|
case R.id.digit_7:
|
return context.getString(R.string.digit_7);
|
case R.id.digit_8:
|
return context.getString(R.string.digit_8);
|
case R.id.digit_9:
|
return context.getString(R.string.digit_9);
|
default:
|
return "";
|
}
|
}
|
|
/**
|
* Map key id to a single byte, somewhat human readable, description.
|
* Used to serialize expressions in the database.
|
* The result is in the range 0x20-0x7f.
|
*/
|
public static byte toByte(int id) {
|
char result;
|
// We only use characters with single-byte UTF8 encodings in the range 0x20-0x7F.
|
switch(id) {
|
case R.id.const_pi:
|
result = 'p';
|
break;
|
case R.id.const_e:
|
result = 'e';
|
break;
|
case R.id.op_sqrt:
|
result = 'r';
|
break;
|
case R.id.op_fact:
|
result = '!';
|
break;
|
case R.id.op_pct:
|
result = '%';
|
break;
|
case R.id.fun_sin:
|
result = 's';
|
break;
|
case R.id.fun_cos:
|
result = 'c';
|
break;
|
case R.id.fun_tan:
|
result = 't';
|
break;
|
case R.id.fun_arcsin:
|
result = 'S';
|
break;
|
case R.id.fun_arccos:
|
result = 'C';
|
break;
|
case R.id.fun_arctan:
|
result = 'T';
|
break;
|
case R.id.fun_ln:
|
result = 'l';
|
break;
|
case R.id.fun_log:
|
result = 'L';
|
break;
|
case R.id.fun_exp:
|
result = 'E';
|
break;
|
case R.id.lparen:
|
result = '(';
|
break;
|
case R.id.rparen:
|
result = ')';
|
break;
|
case R.id.op_pow:
|
result = '^';
|
break;
|
case R.id.op_mul:
|
result = '*';
|
break;
|
case R.id.op_div:
|
result = '/';
|
break;
|
case R.id.op_add:
|
result = '+';
|
break;
|
case R.id.op_sub:
|
result = '-';
|
break;
|
case R.id.op_sqr:
|
result = '2';
|
break;
|
default:
|
throw new AssertionError("Unexpected key id");
|
}
|
return (byte)result;
|
}
|
|
/**
|
* Map single byte encoding generated by key id generated by toByte back to
|
* key id.
|
*/
|
public static int fromByte(byte b) {
|
switch((char)b) {
|
case 'p':
|
return R.id.const_pi;
|
case 'e':
|
return R.id.const_e;
|
case 'r':
|
return R.id.op_sqrt;
|
case '!':
|
return R.id.op_fact;
|
case '%':
|
return R.id.op_pct;
|
case 's':
|
return R.id.fun_sin;
|
case 'c':
|
return R.id.fun_cos;
|
case 't':
|
return R.id.fun_tan;
|
case 'S':
|
return R.id.fun_arcsin;
|
case 'C':
|
return R.id.fun_arccos;
|
case 'T':
|
return R.id.fun_arctan;
|
case 'l':
|
return R.id.fun_ln;
|
case 'L':
|
return R.id.fun_log;
|
case 'E':
|
return R.id.fun_exp;
|
case '(':
|
return R.id.lparen;
|
case ')':
|
return R.id.rparen;
|
case '^':
|
return R.id.op_pow;
|
case '*':
|
return R.id.op_mul;
|
case '/':
|
return R.id.op_div;
|
case '+':
|
return R.id.op_add;
|
case '-':
|
return R.id.op_sub;
|
case '2':
|
return R.id.op_sqr;
|
default:
|
throw new AssertionError("Unexpected single byte operator encoding");
|
}
|
}
|
|
/**
|
* Map key id to corresponding (internationalized) descriptive string that can be used
|
* to correctly read back a formula.
|
* Only used for operators and individual characters; not used inside constants.
|
* Returns null when we don't need a descriptive string.
|
* Pure function.
|
*/
|
public static String toDescriptiveString(Context context, int id) {
|
switch(id) {
|
case R.id.op_fact:
|
return context.getString(R.string.desc_op_fact);
|
case R.id.fun_sin:
|
return context.getString(R.string.desc_fun_sin)
|
+ " " + context.getString(R.string.desc_lparen);
|
case R.id.fun_cos:
|
return context.getString(R.string.desc_fun_cos)
|
+ " " + context.getString(R.string.desc_lparen);
|
case R.id.fun_tan:
|
return context.getString(R.string.desc_fun_tan)
|
+ " " + context.getString(R.string.desc_lparen);
|
case R.id.fun_arcsin:
|
return context.getString(R.string.desc_fun_arcsin)
|
+ " " + context.getString(R.string.desc_lparen);
|
case R.id.fun_arccos:
|
return context.getString(R.string.desc_fun_arccos)
|
+ " " + context.getString(R.string.desc_lparen);
|
case R.id.fun_arctan:
|
return context.getString(R.string.desc_fun_arctan)
|
+ " " + context.getString(R.string.desc_lparen);
|
case R.id.fun_ln:
|
return context.getString(R.string.desc_fun_ln)
|
+ " " + context.getString(R.string.desc_lparen);
|
case R.id.fun_log:
|
return context.getString(R.string.desc_fun_log)
|
+ " " + context.getString(R.string.desc_lparen);
|
case R.id.fun_exp:
|
return context.getString(R.string.desc_fun_exp)
|
+ " " + context.getString(R.string.desc_lparen);
|
case R.id.lparen:
|
return context.getString(R.string.desc_lparen);
|
case R.id.rparen:
|
return context.getString(R.string.desc_rparen);
|
case R.id.op_pow:
|
return context.getString(R.string.desc_op_pow);
|
case R.id.dec_point:
|
return context.getString(R.string.desc_dec_point);
|
default:
|
return null;
|
}
|
}
|
|
/**
|
* Does a button id correspond to a binary operator?
|
* Pure function.
|
*/
|
public static boolean isBinary(int id) {
|
switch(id) {
|
case R.id.op_pow:
|
case R.id.op_mul:
|
case R.id.op_div:
|
case R.id.op_add:
|
case R.id.op_sub:
|
return true;
|
default:
|
return false;
|
}
|
}
|
|
/**
|
* Does a button id correspond to a trig function?
|
* Pure function.
|
*/
|
public static boolean isTrigFunc(int id) {
|
switch(id) {
|
case R.id.fun_sin:
|
case R.id.fun_cos:
|
case R.id.fun_tan:
|
case R.id.fun_arcsin:
|
case R.id.fun_arccos:
|
case R.id.fun_arctan:
|
return true;
|
default:
|
return false;
|
}
|
}
|
|
/**
|
* Does a button id correspond to a function that introduces an implicit lparen?
|
* Pure function.
|
*/
|
public static boolean isFunc(int id) {
|
if (isTrigFunc(id)) {
|
return true;
|
}
|
switch(id) {
|
case R.id.fun_ln:
|
case R.id.fun_log:
|
case R.id.fun_exp:
|
return true;
|
default:
|
return false;
|
}
|
}
|
|
/**
|
* Does a button id correspond to a prefix operator?
|
* Pure function.
|
*/
|
public static boolean isPrefix(int id) {
|
switch(id) {
|
case R.id.op_sqrt:
|
case R.id.op_sub:
|
return true;
|
default:
|
return false;
|
}
|
}
|
|
/**
|
* Does a button id correspond to a suffix operator?
|
*/
|
public static boolean isSuffix(int id) {
|
switch (id) {
|
case R.id.op_fact:
|
case R.id.op_pct:
|
case R.id.op_sqr:
|
return true;
|
default:
|
return false;
|
}
|
}
|
|
public static final int NOT_DIGIT = 10;
|
|
public static final String ELLIPSIS = "\u2026";
|
|
public static final char MINUS_SIGN = '\u2212';
|
|
/**
|
* Map key id to digit or NOT_DIGIT
|
* Pure function.
|
*/
|
public static int digVal(int id) {
|
switch (id) {
|
case R.id.digit_0:
|
return 0;
|
case R.id.digit_1:
|
return 1;
|
case R.id.digit_2:
|
return 2;
|
case R.id.digit_3:
|
return 3;
|
case R.id.digit_4:
|
return 4;
|
case R.id.digit_5:
|
return 5;
|
case R.id.digit_6:
|
return 6;
|
case R.id.digit_7:
|
return 7;
|
case R.id.digit_8:
|
return 8;
|
case R.id.digit_9:
|
return 9;
|
default:
|
return NOT_DIGIT;
|
}
|
}
|
|
/**
|
* Map digit to corresponding key. Inverse of above.
|
* Pure function.
|
*/
|
public static int keyForDigVal(int v) {
|
switch(v) {
|
case 0:
|
return R.id.digit_0;
|
case 1:
|
return R.id.digit_1;
|
case 2:
|
return R.id.digit_2;
|
case 3:
|
return R.id.digit_3;
|
case 4:
|
return R.id.digit_4;
|
case 5:
|
return R.id.digit_5;
|
case 6:
|
return R.id.digit_6;
|
case 7:
|
return R.id.digit_7;
|
case 8:
|
return R.id.digit_8;
|
case 9:
|
return R.id.digit_9;
|
default:
|
return View.NO_ID;
|
}
|
}
|
|
// The following two are only used for recognizing additional
|
// input characters from a physical keyboard. They are not used
|
// for output internationalization.
|
private static char mDecimalPt;
|
|
private static char mPiChar;
|
|
/**
|
* Character used as a placeholder for digits that are currently unknown in a result that
|
* is being computed. We initially generate blanks, and then use this as a replacement
|
* during final translation.
|
* <p/>
|
* Note: the character must correspond closely to the width of a digit,
|
* otherwise the UI will visibly shift once the computation is finished.
|
*/
|
private static final char CHAR_DIGIT_UNKNOWN = '\u2007';
|
|
/**
|
* Map typed function name strings to corresponding button ids.
|
* We (now redundantly?) include both localized and English names.
|
*/
|
private static HashMap<String, Integer> sKeyValForFun;
|
|
/**
|
* Result string corresponding to a character in the calculator result.
|
* The string values in the map are expected to be one character long.
|
*/
|
private static HashMap<Character, String> sOutputForResultChar;
|
|
/**
|
* Locale corresponding to preceding map and character constants.
|
* We recompute the map if this is not the current locale.
|
*/
|
private static Locale sLocaleForMaps = null;
|
|
/**
|
* Activity to use for looking up buttons.
|
*/
|
private static Activity mActivity;
|
|
/**
|
* Set acttivity used for looking up button labels.
|
* Call only from UI thread.
|
*/
|
public static void setActivity(Activity a) {
|
mActivity = a;
|
}
|
|
/**
|
* Return the button id corresponding to the supplied character or return NO_ID.
|
* Called only by UI thread.
|
*/
|
public static int keyForChar(char c) {
|
validateMaps();
|
if (Character.isDigit(c)) {
|
int i = Character.digit(c, 10);
|
return KeyMaps.keyForDigVal(i);
|
}
|
switch (c) {
|
case '.':
|
case ',':
|
return R.id.dec_point;
|
case '-':
|
case MINUS_SIGN:
|
return R.id.op_sub;
|
case '+':
|
return R.id.op_add;
|
case '*':
|
case '\u00D7': // MULTIPLICATION SIGN
|
return R.id.op_mul;
|
case '/':
|
case '\u00F7': // DIVISION SIGN
|
return R.id.op_div;
|
// We no longer localize function names, so they can't start with an 'e' or 'p'.
|
case 'e':
|
case 'E':
|
return R.id.const_e;
|
case 'p':
|
case 'P':
|
return R.id.const_pi;
|
case '^':
|
return R.id.op_pow;
|
case '!':
|
return R.id.op_fact;
|
case '%':
|
return R.id.op_pct;
|
case '(':
|
return R.id.lparen;
|
case ')':
|
return R.id.rparen;
|
default:
|
if (c == mDecimalPt) return R.id.dec_point;
|
if (c == mPiChar) return R.id.const_pi;
|
// pi is not translated, but it might be typable on a Greek keyboard,
|
// or pasted in, so we check ...
|
return View.NO_ID;
|
}
|
}
|
|
/**
|
* Add information corresponding to the given button id to sKeyValForFun, to be used
|
* when mapping keyboard input to button ids.
|
*/
|
static void addButtonToFunMap(int button_id) {
|
Button button = (Button)mActivity.findViewById(button_id);
|
sKeyValForFun.put(button.getText().toString(), button_id);
|
}
|
|
/**
|
* Add information corresponding to the given button to sOutputForResultChar, to be used
|
* when translating numbers on output.
|
*/
|
static void addButtonToOutputMap(char c, int button_id) {
|
Button button = (Button)mActivity.findViewById(button_id);
|
sOutputForResultChar.put(c, button.getText().toString());
|
}
|
|
/**
|
* Ensure that the preceding map and character constants correspond to the current locale.
|
* Called only by UI thread.
|
*/
|
static void validateMaps() {
|
Locale locale = Locale.getDefault();
|
if (!locale.equals(sLocaleForMaps)) {
|
Log.v ("Calculator", "Setting locale to: " + locale.toLanguageTag());
|
sKeyValForFun = new HashMap<String, Integer>();
|
sKeyValForFun.put("sin", R.id.fun_sin);
|
sKeyValForFun.put("cos", R.id.fun_cos);
|
sKeyValForFun.put("tan", R.id.fun_tan);
|
sKeyValForFun.put("arcsin", R.id.fun_arcsin);
|
sKeyValForFun.put("arccos", R.id.fun_arccos);
|
sKeyValForFun.put("arctan", R.id.fun_arctan);
|
sKeyValForFun.put("asin", R.id.fun_arcsin);
|
sKeyValForFun.put("acos", R.id.fun_arccos);
|
sKeyValForFun.put("atan", R.id.fun_arctan);
|
sKeyValForFun.put("ln", R.id.fun_ln);
|
sKeyValForFun.put("log", R.id.fun_log);
|
sKeyValForFun.put("sqrt", R.id.op_sqrt); // special treatment
|
addButtonToFunMap(R.id.fun_sin);
|
addButtonToFunMap(R.id.fun_cos);
|
addButtonToFunMap(R.id.fun_tan);
|
addButtonToFunMap(R.id.fun_arcsin);
|
addButtonToFunMap(R.id.fun_arccos);
|
addButtonToFunMap(R.id.fun_arctan);
|
addButtonToFunMap(R.id.fun_ln);
|
addButtonToFunMap(R.id.fun_log);
|
|
// Set locale-dependent character "constants"
|
mDecimalPt =
|
DecimalFormatSymbols.getInstance().getDecimalSeparator();
|
// We recognize this in keyboard input, even if we use
|
// a different character.
|
Resources res = mActivity.getResources();
|
mPiChar = 0;
|
String piString = res.getString(R.string.const_pi);
|
if (piString.length() == 1) {
|
mPiChar = piString.charAt(0);
|
}
|
|
sOutputForResultChar = new HashMap<Character, String>();
|
sOutputForResultChar.put('e', "E");
|
sOutputForResultChar.put('E', "E");
|
sOutputForResultChar.put(' ', String.valueOf(CHAR_DIGIT_UNKNOWN));
|
sOutputForResultChar.put(ELLIPSIS.charAt(0), ELLIPSIS);
|
// Translate numbers for fraction display, but not the separating slash, which appears
|
// to be universal. We also do not translate the ln, sqrt, pi
|
sOutputForResultChar.put('/', "/");
|
sOutputForResultChar.put('(', "(");
|
sOutputForResultChar.put(')', ")");
|
sOutputForResultChar.put('l', "l");
|
sOutputForResultChar.put('n', "n");
|
sOutputForResultChar.put(',',
|
String.valueOf(DecimalFormatSymbols.getInstance().getGroupingSeparator()));
|
sOutputForResultChar.put('\u221A', "\u221A"); // SQUARE ROOT
|
sOutputForResultChar.put('\u03C0', "\u03C0"); // GREEK SMALL LETTER PI
|
addButtonToOutputMap('-', R.id.op_sub);
|
addButtonToOutputMap('.', R.id.dec_point);
|
for (int i = 0; i <= 9; ++i) {
|
addButtonToOutputMap((char)('0' + i), keyForDigVal(i));
|
}
|
|
sLocaleForMaps = locale;
|
|
}
|
}
|
|
/**
|
* Return function button id for the substring of s starting at pos and ending with
|
* the next "(". Return NO_ID if there is none.
|
* We currently check for both (possibly localized) button labels, and standard
|
* English names. (They should currently be the same, and hence this is currently redundant.)
|
* Callable only from UI thread.
|
*/
|
public static int funForString(String s, int pos) {
|
validateMaps();
|
int parenPos = s.indexOf('(', pos);
|
if (parenPos != -1) {
|
String funString = s.substring(pos, parenPos);
|
Integer keyValue = sKeyValForFun.get(funString);
|
if (keyValue == null) return View.NO_ID;
|
return keyValue;
|
}
|
return View.NO_ID;
|
}
|
|
/**
|
* Return the localization of the string s representing a numeric answer.
|
* Callable only from UI thread.
|
* A trailing e is treated as the mathematical constant, not an exponent.
|
*/
|
public static String translateResult(String s) {
|
StringBuilder result = new StringBuilder();
|
int len = s.length();
|
validateMaps();
|
for (int i = 0; i < len; ++i) {
|
char c = s.charAt(i);
|
if (i < len - 1 || c != 'e') {
|
String translation = sOutputForResultChar.get(c);
|
if (translation == null) {
|
// Should not get here. Report if we do.
|
Log.v("Calculator", "Bad character:" + c);
|
result.append(String.valueOf(c));
|
} else {
|
result.append(translation);
|
}
|
}
|
}
|
return result.toString();
|
}
|
|
}
|