blob: 670f895e4cd207a64eb5f96260e5d36d4e5b3c7d [file] [log] [blame]
Hans Boehm84614952014-11-25 18:46:17 -08001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.calculator2;
18
19import android.content.res.Resources;
20import android.content.Context;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070021import android.app.Activity;
22import android.util.Log;
Hans Boehm84614952014-11-25 18:46:17 -080023import android.view.View;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070024import android.widget.Button;
25
Hans Boehm84614952014-11-25 18:46:17 -080026import java.text.DecimalFormatSymbols;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070027import java.util.HashMap;
28import java.util.Locale;
Hans Boehm84614952014-11-25 18:46:17 -080029
Hans Boehmffda5282015-05-18 15:00:12 -070030// This is a collection of various mapping functions between key ids,
31// characters, internationalized and noninternationalized characters, etc.
32//
Hans Boehm013969e2015-04-13 20:29:47 -070033// KeyMap instances are not meaningful; everything here is static.
34// All functions are either pure, or are assumed to be called only from
35// a single UI thread.
36
Hans Boehm84614952014-11-25 18:46:17 -080037public class KeyMaps {
Hans Boehm013969e2015-04-13 20:29:47 -070038 // Map key id to corresponding (internationalized) display string.
39 // Pure function.
Hans Boehm84614952014-11-25 18:46:17 -080040 public static String toString(int id, Context context) {
41 Resources res = context.getResources();
42 switch(id) {
43 case R.id.const_pi: return res.getString(R.string.const_pi);
44 case R.id.const_e: return res.getString(R.string.const_e);
45 case R.id.op_sqrt: return res.getString(R.string.op_sqrt);
46 case R.id.op_fact: return res.getString(R.string.op_fact);
47 case R.id.fun_sin: return res.getString(R.string.fun_sin)
48 + res.getString(R.string.lparen);
Hans Boehm013969e2015-04-13 20:29:47 -070049 case R.id.fun_cos: return res.getString(R.string.fun_cos)
Hans Boehm84614952014-11-25 18:46:17 -080050 + res.getString(R.string.lparen);
51 case R.id.fun_tan: return res.getString(R.string.fun_tan)
52 + res.getString(R.string.lparen);
53 case R.id.fun_arcsin: return res.getString(R.string.fun_arcsin)
54 + res.getString(R.string.lparen);
Hans Boehm013969e2015-04-13 20:29:47 -070055 case R.id.fun_arccos: return res.getString(R.string.fun_arccos)
Hans Boehm84614952014-11-25 18:46:17 -080056 + res.getString(R.string.lparen);
57 case R.id.fun_arctan: return res.getString(R.string.fun_arctan)
58 + res.getString(R.string.lparen);
59 case R.id.fun_ln: return res.getString(R.string.fun_ln)
60 + res.getString(R.string.lparen);
61 case R.id.fun_log: return res.getString(R.string.fun_log)
62 + res.getString(R.string.lparen);
63 case R.id.lparen: return res.getString(R.string.lparen);
64 case R.id.rparen: return res.getString(R.string.rparen);
65 case R.id.op_pow: return res.getString(R.string.op_pow);
66 case R.id.op_mul: return res.getString(R.string.op_mul);
67 case R.id.op_div: return res.getString(R.string.op_div);
68 case R.id.op_add: return res.getString(R.string.op_add);
69 case R.id.op_sub: return res.getString(R.string.op_sub);
70 case R.id.dec_point: return res.getString(R.string.dec_point);
71 case R.id.digit_0: return res.getString(R.string.digit_0);
72 case R.id.digit_1: return res.getString(R.string.digit_1);
73 case R.id.digit_2: return res.getString(R.string.digit_2);
74 case R.id.digit_3: return res.getString(R.string.digit_3);
75 case R.id.digit_4: return res.getString(R.string.digit_4);
76 case R.id.digit_5: return res.getString(R.string.digit_5);
77 case R.id.digit_6: return res.getString(R.string.digit_6);
78 case R.id.digit_7: return res.getString(R.string.digit_7);
79 case R.id.digit_8: return res.getString(R.string.digit_8);
80 case R.id.digit_9: return res.getString(R.string.digit_9);
81 default: return "?oops?";
82 }
83 }
84
85 // Does a button id correspond to a binary operator?
Hans Boehm013969e2015-04-13 20:29:47 -070086 // Pure function.
Hans Boehm84614952014-11-25 18:46:17 -080087 public static boolean isBinary(int id) {
88 switch(id) {
89 case R.id.op_pow:
90 case R.id.op_mul:
91 case R.id.op_div:
92 case R.id.op_add:
93 case R.id.op_sub:
94 return true;
95 default:
96 return false;
97 }
98 }
99
100 // Does a button id correspond to a suffix operator?
101 public static boolean isSuffix(int id) {
102 return id == R.id.op_fact;
103 }
104
105 public static final int NOT_DIGIT = 10;
106
Hans Boehm08e8f322015-04-21 13:18:38 -0700107 public static final String ELLIPSIS = "\u2026";
108
Hans Boehm84614952014-11-25 18:46:17 -0800109 // Map key id to digit or NOT_DIGIT
Hans Boehm013969e2015-04-13 20:29:47 -0700110 // Pure function.
Hans Boehm84614952014-11-25 18:46:17 -0800111 public static int digVal(int id) {
112 switch (id) {
113 case R.id.digit_0:
114 return 0;
115 case R.id.digit_1:
116 return 1;
117 case R.id.digit_2:
118 return 2;
119 case R.id.digit_3:
120 return 3;
121 case R.id.digit_4:
122 return 4;
123 case R.id.digit_5:
124 return 5;
125 case R.id.digit_6:
126 return 6;
127 case R.id.digit_7:
128 return 7;
129 case R.id.digit_8:
130 return 8;
131 case R.id.digit_9:
132 return 9;
133 default:
134 return NOT_DIGIT;
135 }
136 }
137
138 // Map digit to corresponding key. Inverse of above.
Hans Boehm013969e2015-04-13 20:29:47 -0700139 // Pure function.
Hans Boehm84614952014-11-25 18:46:17 -0800140 public static int keyForDigVal(int v) {
141 switch(v) {
142 case 0:
143 return R.id.digit_0;
144 case 1:
145 return R.id.digit_1;
146 case 2:
147 return R.id.digit_2;
148 case 3:
149 return R.id.digit_3;
150 case 4:
151 return R.id.digit_4;
152 case 5:
153 return R.id.digit_5;
154 case 6:
155 return R.id.digit_6;
156 case 7:
157 return R.id.digit_7;
158 case 8:
159 return R.id.digit_8;
160 case 9:
161 return R.id.digit_9;
162 default:
163 return View.NO_ID;
164 }
165 }
166
Hans Boehm425ed0a2015-05-19 18:27:31 -0700167 // The following two are only used for recognizing additional
168 // input characters from a physical keyboard. They are not used
169 // for output internationalization.
170 private static char mDecimalPt;
Hans Boehm84614952014-11-25 18:46:17 -0800171
Hans Boehmffda5282015-05-18 15:00:12 -0700172 private static char mPiChar;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700173
Hans Boehmffda5282015-05-18 15:00:12 -0700174 /**
175 * Character used as a placeholder for digits that are currently unknown
176 * in a result that is being computed. We initially generate blanks, and
177 * then use this as a replacement during final translation.
178 * <p/>
179 * Note: the character must correspond closely to the width of a digit,
180 * otherwise the UI will visibly shift once the computation is finished.
181 */
182 private static final char CHAR_DIGIT_UNKNOWN = '\u2007';
183
184 private static HashMap<String, Integer> sKeyValForFun;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700185 // Key value corresponding to given function name.
186 // We include both localized and English names.
187
Hans Boehmffda5282015-05-18 15:00:12 -0700188 private static HashMap<Character, String> sOutputForResultChar;
Hans Boehm013969e2015-04-13 20:29:47 -0700189 // Result string corresponding to a character in the
190 // calculator result.
191 // The string values in the map are expected to be one character
192 // long.
193
Hans Boehmffda5282015-05-18 15:00:12 -0700194 private static String sLocaleForMaps = "none";
Hans Boehm013969e2015-04-13 20:29:47 -0700195 // Locale string corresponding to preceding map and character
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700196 // constants.
197 // We recompute the map if this is not the current locale.
198
Hans Boehmffda5282015-05-18 15:00:12 -0700199 private static Activity mActivity; // Activity to use for looking up
200 // buttons.
Hans Boehm013969e2015-04-13 20:29:47 -0700201
202 // Called only by UI thread.
203 public static void setActivity(Activity a) {
204 mActivity = a;
205 }
206
Hans Boehm84614952014-11-25 18:46:17 -0800207 // Return the button id corresponding to the supplied character
Hans Boehm425ed0a2015-05-19 18:27:31 -0700208 // or return NO_ID.
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700209 // Called only by UI thread.
Hans Boehm013969e2015-04-13 20:29:47 -0700210 public static int keyForChar(char c) {
211 validateMaps();
Hans Boehm84614952014-11-25 18:46:17 -0800212 if (Character.isDigit(c)) {
213 int i = Character.digit(c, 10);
214 return KeyMaps.keyForDigVal(i);
215 }
Hans Boehm84614952014-11-25 18:46:17 -0800216 switch (c) {
217 case '.':
Hans Boehm425ed0a2015-05-19 18:27:31 -0700218 case ',':
Hans Boehm84614952014-11-25 18:46:17 -0800219 return R.id.dec_point;
220 case '-':
221 return R.id.op_sub;
222 case '+':
223 return R.id.op_add;
224 case '*':
225 return R.id.op_mul;
226 case '/':
227 return R.id.op_div;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700228 // TODO: We have an issue if any of the localized function
229 // names start with 'e' or 'p'. That doesn't currently appear
230 // to be the case. In fact the first letters of the Latin
231 // allphabet ones seem rather predictable.
Hans Boehm84614952014-11-25 18:46:17 -0800232 case 'e':
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700233 case 'E':
Hans Boehm84614952014-11-25 18:46:17 -0800234 return R.id.const_e;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700235 case 'p':
236 case 'P':
237 return R.id.const_pi;
Hans Boehm84614952014-11-25 18:46:17 -0800238 case '^':
239 return R.id.op_pow;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700240 case '!':
241 return R.id.op_fact;
242 case '(':
243 return R.id.lparen;
244 case ')':
245 return R.id.rparen;
Hans Boehm84614952014-11-25 18:46:17 -0800246 default:
Hans Boehm013969e2015-04-13 20:29:47 -0700247 if (c == mDecimalPt) return R.id.dec_point;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700248 if (c == mPiChar) return R.id.const_pi;
249 // pi is not translated, but it might be typable on
250 // a Greek keyboard, so we check ...
Hans Boehm84614952014-11-25 18:46:17 -0800251 return View.NO_ID;
252 }
253 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700254
255 // Add information corresponding to the given button id to
256 // sKeyValForFun.
Hans Boehm013969e2015-04-13 20:29:47 -0700257 static void addButtonToFunMap(int button_id) {
258 Button button = (Button)mActivity.findViewById(button_id);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700259 sKeyValForFun.put(button.getText().toString(), button_id);
260 }
261
Hans Boehm013969e2015-04-13 20:29:47 -0700262 // Ditto, but for sOutputForResultChar.
263 static void addButtonToOutputMap(char c, int button_id) {
264 Button button = (Button)mActivity.findViewById(button_id);
265 sOutputForResultChar.put(c, button.getText().toString());
266 }
267
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700268 // Ensure that the preceding map and character constants are
269 // initialized and correspond to the current locale.
270 // Called only by a single thread, namely the UI thread.
Hans Boehm013969e2015-04-13 20:29:47 -0700271 static void validateMaps() {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700272 Locale locale = Locale.getDefault();
273 String lname = locale.toString();
Hans Boehm013969e2015-04-13 20:29:47 -0700274 if (lname != sLocaleForMaps) {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700275 Log.v ("Calculator", "Setting local to: " + lname);
276 sKeyValForFun = new HashMap<String, Integer>();
277 sKeyValForFun.put("sin", R.id.fun_sin);
278 sKeyValForFun.put("cos", R.id.fun_cos);
279 sKeyValForFun.put("tan", R.id.fun_tan);
280 sKeyValForFun.put("arcsin", R.id.fun_arcsin);
281 sKeyValForFun.put("arccos", R.id.fun_arccos);
282 sKeyValForFun.put("arctan", R.id.fun_arctan);
283 sKeyValForFun.put("asin", R.id.fun_arcsin);
284 sKeyValForFun.put("acos", R.id.fun_arccos);
285 sKeyValForFun.put("atan", R.id.fun_arctan);
286 sKeyValForFun.put("ln", R.id.fun_ln);
287 sKeyValForFun.put("log", R.id.fun_log);
288 sKeyValForFun.put("sqrt", R.id.op_sqrt); // special treatment
Hans Boehm013969e2015-04-13 20:29:47 -0700289 addButtonToFunMap(R.id.fun_sin);
290 addButtonToFunMap(R.id.fun_cos);
291 addButtonToFunMap(R.id.fun_tan);
292 addButtonToFunMap(R.id.fun_arcsin);
293 addButtonToFunMap(R.id.fun_arccos);
294 addButtonToFunMap(R.id.fun_arctan);
295 addButtonToFunMap(R.id.fun_ln);
296 addButtonToFunMap(R.id.fun_log);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700297
298 // Set locale-dependent character "constants"
Hans Boehm013969e2015-04-13 20:29:47 -0700299 mDecimalPt =
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700300 DecimalFormatSymbols.getInstance().getDecimalSeparator();
Hans Boehm425ed0a2015-05-19 18:27:31 -0700301 // We recognize this in keyboard input, even if we use
302 // a different character.
Hans Boehm013969e2015-04-13 20:29:47 -0700303 Resources res = mActivity.getResources();
Hans Boehm425ed0a2015-05-19 18:27:31 -0700304 mPiChar = 0;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700305 String piString = res.getString(R.string.const_pi);
306 if (piString.length() == 1) mPiChar = piString.charAt(0);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700307
Hans Boehm013969e2015-04-13 20:29:47 -0700308 sOutputForResultChar = new HashMap<Character, String>();
309 sOutputForResultChar.put('e', "E");
310 sOutputForResultChar.put('E', "E");
Hans Boehmffda5282015-05-18 15:00:12 -0700311 sOutputForResultChar.put(' ', String.valueOf(CHAR_DIGIT_UNKNOWN));
Hans Boehm08e8f322015-04-21 13:18:38 -0700312 sOutputForResultChar.put(ELLIPSIS.charAt(0), ELLIPSIS);
Hans Boehm013969e2015-04-13 20:29:47 -0700313 sOutputForResultChar.put('/', "/");
314 // Translate numbers for fraction display, but not
315 // the separating slash, which appears to be
316 // universal.
317 addButtonToOutputMap('-', R.id.op_sub);
Hans Boehm425ed0a2015-05-19 18:27:31 -0700318 addButtonToOutputMap('.', R.id.dec_point);
Hans Boehm013969e2015-04-13 20:29:47 -0700319 for (int i = 0; i <= 9; ++i) {
320 addButtonToOutputMap((char)('0' + i), keyForDigVal(i));
321 }
322
323 sLocaleForMaps = lname;
324
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700325 }
326 }
327
328 // Return function button id for the substring of s starting
329 // at pos and ending with the next "(".
330 // Return NO_ID if there is none.
331 // We check for both standard English names and localized
332 // button labels, though those don't seem to differ much.
333 // Called only by a single thread, namely the UI thread.
Hans Boehm013969e2015-04-13 20:29:47 -0700334 public static int funForString(String s, int pos) {
335 validateMaps();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700336 int parenPos = s.indexOf('(', pos);
337 if (parenPos != -1) {
338 String funString = s.substring(pos, parenPos);
339 Integer keyValue = sKeyValForFun.get(funString);
340 if (keyValue == null) return View.NO_ID;
341 return keyValue;
342 }
343 return View.NO_ID;
344 }
Hans Boehm013969e2015-04-13 20:29:47 -0700345
346 // Called only by UI thread.
347 public static String translateResult(String s) {
348 StringBuilder result = new StringBuilder();
349 int len = s.length();
350 validateMaps();
351 for (int i = 0; i < len; ++i) {
352 char c = s.charAt(i);
353 String translation = sOutputForResultChar.get(c);
354 if (translation == null) {
355 // Should not get here.
356 Log.v("Calculator", "Bad character:" + c);
357 result.append(String.valueOf(c));
358 } else {
359 result.append(translation);
360 }
361 }
362 return result.toString();
363 }
364
Hans Boehm84614952014-11-25 18:46:17 -0800365}