blob: 9a84612a3b9038c3f3ba0e5e66db3407bb7c953d [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 Boehm0b3a9fd2015-05-22 11:36:26 -070030/**
31 * Collection of mapping functions between key ids, characters, internationalized
32 * and noninternationalized characters, etc.
33 * <p>
34 * KeyMap instances are not meaningful; everything here is static.
35 * All functions are either pure, or are assumed to be called only from a single UI thread.
36 */
Hans Boehm84614952014-11-25 18:46:17 -080037public class KeyMaps {
Hans Boehm0b3a9fd2015-05-22 11:36:26 -070038 /**
39 * Map key id to corresponding (internationalized) display string.
40 * Pure function.
41 */
Hans Boehm84614952014-11-25 18:46:17 -080042 public static String toString(int id, Context context) {
43 Resources res = context.getResources();
44 switch(id) {
Hans Boehm0b3a9fd2015-05-22 11:36:26 -070045 case R.id.const_pi:
46 return res.getString(R.string.const_pi);
47 case R.id.const_e:
48 return res.getString(R.string.const_e);
49 case R.id.op_sqrt:
50 return res.getString(R.string.op_sqrt);
51 case R.id.op_fact:
52 return res.getString(R.string.op_fact);
53 case R.id.fun_sin:
54 return res.getString(R.string.fun_sin) + res.getString(R.string.lparen);
55 case R.id.fun_cos:
56 return res.getString(R.string.fun_cos) + res.getString(R.string.lparen);
57 case R.id.fun_tan:
58 return res.getString(R.string.fun_tan) + res.getString(R.string.lparen);
59 case R.id.fun_arcsin:
60 return res.getString(R.string.fun_arcsin) + res.getString(R.string.lparen);
61 case R.id.fun_arccos:
62 return res.getString(R.string.fun_arccos) + res.getString(R.string.lparen);
63 case R.id.fun_arctan:
64 return res.getString(R.string.fun_arctan) + res.getString(R.string.lparen);
65 case R.id.fun_ln:
66 return res.getString(R.string.fun_ln) + res.getString(R.string.lparen);
67 case R.id.fun_log:
68 return res.getString(R.string.fun_log) + res.getString(R.string.lparen);
69 case R.id.lparen:
70 return res.getString(R.string.lparen);
71 case R.id.rparen:
72 return res.getString(R.string.rparen);
73 case R.id.op_pow:
74 return res.getString(R.string.op_pow);
75 case R.id.op_mul:
76 return res.getString(R.string.op_mul);
77 case R.id.op_div:
78 return res.getString(R.string.op_div);
79 case R.id.op_add:
80 return res.getString(R.string.op_add);
81 case R.id.op_sub:
82 return res.getString(R.string.op_sub);
83 case R.id.dec_point:
84 return res.getString(R.string.dec_point);
85 case R.id.digit_0:
86 return res.getString(R.string.digit_0);
87 case R.id.digit_1:
88 return res.getString(R.string.digit_1);
89 case R.id.digit_2:
90 return res.getString(R.string.digit_2);
91 case R.id.digit_3:
92 return res.getString(R.string.digit_3);
93 case R.id.digit_4:
94 return res.getString(R.string.digit_4);
95 case R.id.digit_5:
96 return res.getString(R.string.digit_5);
97 case R.id.digit_6:
98 return res.getString(R.string.digit_6);
99 case R.id.digit_7:
100 return res.getString(R.string.digit_7);
101 case R.id.digit_8:
102 return res.getString(R.string.digit_8);
103 case R.id.digit_9:
104 return res.getString(R.string.digit_9);
105 default:
106 return "?oops?";
Hans Boehm84614952014-11-25 18:46:17 -0800107 }
108 }
109
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700110 /**
111 * Does a button id correspond to a binary operator?
112 * Pure function.
113 */
Hans Boehm84614952014-11-25 18:46:17 -0800114 public static boolean isBinary(int id) {
115 switch(id) {
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700116 case R.id.op_pow:
117 case R.id.op_mul:
118 case R.id.op_div:
119 case R.id.op_add:
120 case R.id.op_sub:
121 return true;
122 default:
123 return false;
Hans Boehm84614952014-11-25 18:46:17 -0800124 }
125 }
126
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700127 /**
128 * Does a button id correspond to a suffix operator?
129 */
Hans Boehm84614952014-11-25 18:46:17 -0800130 public static boolean isSuffix(int id) {
131 return id == R.id.op_fact;
132 }
133
134 public static final int NOT_DIGIT = 10;
135
Hans Boehm08e8f322015-04-21 13:18:38 -0700136 public static final String ELLIPSIS = "\u2026";
137
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700138 /**
139 * Map key id to digit or NOT_DIGIT
140 * Pure function.
141 */
Hans Boehm84614952014-11-25 18:46:17 -0800142 public static int digVal(int id) {
143 switch (id) {
144 case R.id.digit_0:
145 return 0;
146 case R.id.digit_1:
147 return 1;
148 case R.id.digit_2:
149 return 2;
150 case R.id.digit_3:
151 return 3;
152 case R.id.digit_4:
153 return 4;
154 case R.id.digit_5:
155 return 5;
156 case R.id.digit_6:
157 return 6;
158 case R.id.digit_7:
159 return 7;
160 case R.id.digit_8:
161 return 8;
162 case R.id.digit_9:
163 return 9;
164 default:
165 return NOT_DIGIT;
166 }
167 }
168
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700169 /**
170 * Map digit to corresponding key. Inverse of above.
171 * Pure function.
172 */
Hans Boehm84614952014-11-25 18:46:17 -0800173 public static int keyForDigVal(int v) {
174 switch(v) {
175 case 0:
176 return R.id.digit_0;
177 case 1:
178 return R.id.digit_1;
179 case 2:
180 return R.id.digit_2;
181 case 3:
182 return R.id.digit_3;
183 case 4:
184 return R.id.digit_4;
185 case 5:
186 return R.id.digit_5;
187 case 6:
188 return R.id.digit_6;
189 case 7:
190 return R.id.digit_7;
191 case 8:
192 return R.id.digit_8;
193 case 9:
194 return R.id.digit_9;
195 default:
196 return View.NO_ID;
197 }
198 }
199
Hans Boehm425ed0a2015-05-19 18:27:31 -0700200 // The following two are only used for recognizing additional
201 // input characters from a physical keyboard. They are not used
202 // for output internationalization.
203 private static char mDecimalPt;
Hans Boehm84614952014-11-25 18:46:17 -0800204
Hans Boehmffda5282015-05-18 15:00:12 -0700205 private static char mPiChar;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700206
Hans Boehmffda5282015-05-18 15:00:12 -0700207 /**
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700208 * Character used as a placeholder for digits that are currently unknown in a result that
209 * is being computed. We initially generate blanks, and then use this as a replacement
210 * during final translation.
Hans Boehmffda5282015-05-18 15:00:12 -0700211 * <p/>
212 * Note: the character must correspond closely to the width of a digit,
213 * otherwise the UI will visibly shift once the computation is finished.
214 */
215 private static final char CHAR_DIGIT_UNKNOWN = '\u2007';
216
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700217 /**
218 * Map typed function name strings to corresponding button ids.
219 * We (now redundantly?) include both localized and English names.
220 */
Hans Boehmffda5282015-05-18 15:00:12 -0700221 private static HashMap<String, Integer> sKeyValForFun;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700222
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700223 /**
224 * Result string corresponding to a character in the calculator result.
225 * The string values in the map are expected to be one character long.
226 */
Hans Boehmffda5282015-05-18 15:00:12 -0700227 private static HashMap<Character, String> sOutputForResultChar;
Hans Boehm013969e2015-04-13 20:29:47 -0700228
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700229 /**
230 * Locale string corresponding to preceding map and character constants.
231 * We recompute the map if this is not the current locale.
232 */
Hans Boehmffda5282015-05-18 15:00:12 -0700233 private static String sLocaleForMaps = "none";
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700234
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700235 /**
236 * Activity to use for looking up buttons.
237 */
238 private static Activity mActivity;
Hans Boehm013969e2015-04-13 20:29:47 -0700239
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700240 /**
241 * Set acttivity used for looking up button labels.
242 * Call only from UI thread.
243 */
Hans Boehm013969e2015-04-13 20:29:47 -0700244 public static void setActivity(Activity a) {
245 mActivity = a;
246 }
247
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700248 /**
249 * Return the button id corresponding to the supplied character or return NO_ID.
250 * Called only by UI thread.
251 */
Hans Boehm013969e2015-04-13 20:29:47 -0700252 public static int keyForChar(char c) {
253 validateMaps();
Hans Boehm84614952014-11-25 18:46:17 -0800254 if (Character.isDigit(c)) {
255 int i = Character.digit(c, 10);
256 return KeyMaps.keyForDigVal(i);
257 }
Hans Boehm84614952014-11-25 18:46:17 -0800258 switch (c) {
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700259 case '.':
260 case ',':
261 return R.id.dec_point;
262 case '-':
263 return R.id.op_sub;
264 case '+':
265 return R.id.op_add;
266 case '*':
267 return R.id.op_mul;
268 case '/':
269 return R.id.op_div;
270 // We no longer localize function names, so they can't start with an 'e' or 'p'.
271 case 'e':
272 case 'E':
273 return R.id.const_e;
274 case 'p':
275 case 'P':
276 return R.id.const_pi;
277 case '^':
278 return R.id.op_pow;
279 case '!':
280 return R.id.op_fact;
281 case '(':
282 return R.id.lparen;
283 case ')':
284 return R.id.rparen;
285 default:
286 if (c == mDecimalPt) return R.id.dec_point;
287 if (c == mPiChar) return R.id.const_pi;
288 // pi is not translated, but it might be typable on a Greek keyboard,
289 // so we check ...
290 return View.NO_ID;
Hans Boehm84614952014-11-25 18:46:17 -0800291 }
292 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700293
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700294 /**
295 * Add information corresponding to the given button id to sKeyValForFun, to be used
296 * when mapping keyboard input to button ids.
297 */
Hans Boehm013969e2015-04-13 20:29:47 -0700298 static void addButtonToFunMap(int button_id) {
299 Button button = (Button)mActivity.findViewById(button_id);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700300 sKeyValForFun.put(button.getText().toString(), button_id);
301 }
302
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700303 /**
304 * Add information corresponding to the given button to sOutputForResultChar, to be used
305 * when translating numbers on output.
306 */
Hans Boehm013969e2015-04-13 20:29:47 -0700307 static void addButtonToOutputMap(char c, int button_id) {
308 Button button = (Button)mActivity.findViewById(button_id);
309 sOutputForResultChar.put(c, button.getText().toString());
310 }
311
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700312 // Ensure that the preceding map and character constants are
313 // initialized and correspond to the current locale.
314 // Called only by a single thread, namely the UI thread.
Hans Boehm013969e2015-04-13 20:29:47 -0700315 static void validateMaps() {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700316 Locale locale = Locale.getDefault();
317 String lname = locale.toString();
Hans Boehm013969e2015-04-13 20:29:47 -0700318 if (lname != sLocaleForMaps) {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700319 Log.v ("Calculator", "Setting local to: " + lname);
320 sKeyValForFun = new HashMap<String, Integer>();
321 sKeyValForFun.put("sin", R.id.fun_sin);
322 sKeyValForFun.put("cos", R.id.fun_cos);
323 sKeyValForFun.put("tan", R.id.fun_tan);
324 sKeyValForFun.put("arcsin", R.id.fun_arcsin);
325 sKeyValForFun.put("arccos", R.id.fun_arccos);
326 sKeyValForFun.put("arctan", R.id.fun_arctan);
327 sKeyValForFun.put("asin", R.id.fun_arcsin);
328 sKeyValForFun.put("acos", R.id.fun_arccos);
329 sKeyValForFun.put("atan", R.id.fun_arctan);
330 sKeyValForFun.put("ln", R.id.fun_ln);
331 sKeyValForFun.put("log", R.id.fun_log);
332 sKeyValForFun.put("sqrt", R.id.op_sqrt); // special treatment
Hans Boehm013969e2015-04-13 20:29:47 -0700333 addButtonToFunMap(R.id.fun_sin);
334 addButtonToFunMap(R.id.fun_cos);
335 addButtonToFunMap(R.id.fun_tan);
336 addButtonToFunMap(R.id.fun_arcsin);
337 addButtonToFunMap(R.id.fun_arccos);
338 addButtonToFunMap(R.id.fun_arctan);
339 addButtonToFunMap(R.id.fun_ln);
340 addButtonToFunMap(R.id.fun_log);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700341
342 // Set locale-dependent character "constants"
Hans Boehm013969e2015-04-13 20:29:47 -0700343 mDecimalPt =
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700344 DecimalFormatSymbols.getInstance().getDecimalSeparator();
Hans Boehm425ed0a2015-05-19 18:27:31 -0700345 // We recognize this in keyboard input, even if we use
346 // a different character.
Hans Boehm013969e2015-04-13 20:29:47 -0700347 Resources res = mActivity.getResources();
Hans Boehm425ed0a2015-05-19 18:27:31 -0700348 mPiChar = 0;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700349 String piString = res.getString(R.string.const_pi);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700350 if (piString.length() == 1) {
351 mPiChar = piString.charAt(0);
352 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700353
Hans Boehm013969e2015-04-13 20:29:47 -0700354 sOutputForResultChar = new HashMap<Character, String>();
355 sOutputForResultChar.put('e', "E");
356 sOutputForResultChar.put('E', "E");
Hans Boehmffda5282015-05-18 15:00:12 -0700357 sOutputForResultChar.put(' ', String.valueOf(CHAR_DIGIT_UNKNOWN));
Hans Boehm08e8f322015-04-21 13:18:38 -0700358 sOutputForResultChar.put(ELLIPSIS.charAt(0), ELLIPSIS);
Hans Boehm013969e2015-04-13 20:29:47 -0700359 sOutputForResultChar.put('/', "/");
360 // Translate numbers for fraction display, but not
361 // the separating slash, which appears to be
362 // universal.
363 addButtonToOutputMap('-', R.id.op_sub);
Hans Boehm425ed0a2015-05-19 18:27:31 -0700364 addButtonToOutputMap('.', R.id.dec_point);
Hans Boehm013969e2015-04-13 20:29:47 -0700365 for (int i = 0; i <= 9; ++i) {
366 addButtonToOutputMap((char)('0' + i), keyForDigVal(i));
367 }
368
369 sLocaleForMaps = lname;
370
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700371 }
372 }
373
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700374 /**
375 * Return function button id for the substring of s starting at pos and ending with
376 * the next "(". Return NO_ID if there is none.
377 * We currently check for both (possibly localized) button labels, and standard
378 * English names. (They should currently be the same, and hence this is currently redundant.)
379 * Callable only from UI thread.
380 */
Hans Boehm013969e2015-04-13 20:29:47 -0700381 public static int funForString(String s, int pos) {
382 validateMaps();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700383 int parenPos = s.indexOf('(', pos);
384 if (parenPos != -1) {
385 String funString = s.substring(pos, parenPos);
386 Integer keyValue = sKeyValForFun.get(funString);
387 if (keyValue == null) return View.NO_ID;
388 return keyValue;
389 }
390 return View.NO_ID;
391 }
Hans Boehm013969e2015-04-13 20:29:47 -0700392
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700393 /**
394 * Return the localization of the string s representing a numeric answer.
395 * Callable only from UI thread.
396 */
Hans Boehm013969e2015-04-13 20:29:47 -0700397 public static String translateResult(String s) {
398 StringBuilder result = new StringBuilder();
399 int len = s.length();
400 validateMaps();
401 for (int i = 0; i < len; ++i) {
402 char c = s.charAt(i);
403 String translation = sOutputForResultChar.get(c);
404 if (translation == null) {
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700405 // Should not get here. Report if we do.
Hans Boehm013969e2015-04-13 20:29:47 -0700406 Log.v("Calculator", "Bad character:" + c);
407 result.append(String.valueOf(c));
408 } else {
409 result.append(translation);
410 }
411 }
412 return result.toString();
413 }
414
Hans Boehm84614952014-11-25 18:46:17 -0800415}