blob: e3b84e7c916355f2c2151db826a2a79b474525f1 [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
Justin Klaassene2711cb2015-05-28 11:13:17 -070032 * and non-internationalized characters, etc.
Hans Boehm0b3a9fd2015-05-22 11:36:26 -070033 * <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 */
Justin Klaassene2711cb2015-05-28 11:13:17 -070042 public static String toString(Context context, int id) {
Hans Boehm84614952014-11-25 18:46:17 -080043 switch(id) {
Hans Boehm0b3a9fd2015-05-22 11:36:26 -070044 case R.id.const_pi:
Justin Klaassene2711cb2015-05-28 11:13:17 -070045 return context.getString(R.string.const_pi);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -070046 case R.id.const_e:
Justin Klaassene2711cb2015-05-28 11:13:17 -070047 return context.getString(R.string.const_e);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -070048 case R.id.op_sqrt:
Justin Klaassene2711cb2015-05-28 11:13:17 -070049 return context.getString(R.string.op_sqrt);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -070050 case R.id.op_fact:
Justin Klaassene2711cb2015-05-28 11:13:17 -070051 return context.getString(R.string.op_fact);
52 case R.id.op_pct:
53 return context.getString(R.string.op_pct);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -070054 case R.id.fun_sin:
Justin Klaassene2711cb2015-05-28 11:13:17 -070055 return context.getString(R.string.fun_sin) + context.getString(R.string.lparen);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -070056 case R.id.fun_cos:
Justin Klaassene2711cb2015-05-28 11:13:17 -070057 return context.getString(R.string.fun_cos) + context.getString(R.string.lparen);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -070058 case R.id.fun_tan:
Justin Klaassene2711cb2015-05-28 11:13:17 -070059 return context.getString(R.string.fun_tan) + context.getString(R.string.lparen);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -070060 case R.id.fun_arcsin:
Justin Klaassene2711cb2015-05-28 11:13:17 -070061 return context.getString(R.string.fun_arcsin) + context.getString(R.string.lparen);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -070062 case R.id.fun_arccos:
Justin Klaassene2711cb2015-05-28 11:13:17 -070063 return context.getString(R.string.fun_arccos) + context.getString(R.string.lparen);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -070064 case R.id.fun_arctan:
Justin Klaassene2711cb2015-05-28 11:13:17 -070065 return context.getString(R.string.fun_arctan) + context.getString(R.string.lparen);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -070066 case R.id.fun_ln:
Justin Klaassene2711cb2015-05-28 11:13:17 -070067 return context.getString(R.string.fun_ln) + context.getString(R.string.lparen);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -070068 case R.id.fun_log:
Justin Klaassene2711cb2015-05-28 11:13:17 -070069 return context.getString(R.string.fun_log) + context.getString(R.string.lparen);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -070070 case R.id.lparen:
Justin Klaassene2711cb2015-05-28 11:13:17 -070071 return context.getString(R.string.lparen);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -070072 case R.id.rparen:
Justin Klaassene2711cb2015-05-28 11:13:17 -070073 return context.getString(R.string.rparen);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -070074 case R.id.op_pow:
Justin Klaassene2711cb2015-05-28 11:13:17 -070075 return context.getString(R.string.op_pow);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -070076 case R.id.op_mul:
Justin Klaassene2711cb2015-05-28 11:13:17 -070077 return context.getString(R.string.op_mul);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -070078 case R.id.op_div:
Justin Klaassene2711cb2015-05-28 11:13:17 -070079 return context.getString(R.string.op_div);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -070080 case R.id.op_add:
Justin Klaassene2711cb2015-05-28 11:13:17 -070081 return context.getString(R.string.op_add);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -070082 case R.id.op_sub:
Justin Klaassene2711cb2015-05-28 11:13:17 -070083 return context.getString(R.string.op_sub);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -070084 case R.id.dec_point:
Justin Klaassene2711cb2015-05-28 11:13:17 -070085 return context.getString(R.string.dec_point);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -070086 case R.id.digit_0:
Justin Klaassene2711cb2015-05-28 11:13:17 -070087 return context.getString(R.string.digit_0);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -070088 case R.id.digit_1:
Justin Klaassene2711cb2015-05-28 11:13:17 -070089 return context.getString(R.string.digit_1);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -070090 case R.id.digit_2:
Justin Klaassene2711cb2015-05-28 11:13:17 -070091 return context.getString(R.string.digit_2);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -070092 case R.id.digit_3:
Justin Klaassene2711cb2015-05-28 11:13:17 -070093 return context.getString(R.string.digit_3);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -070094 case R.id.digit_4:
Justin Klaassene2711cb2015-05-28 11:13:17 -070095 return context.getString(R.string.digit_4);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -070096 case R.id.digit_5:
Justin Klaassene2711cb2015-05-28 11:13:17 -070097 return context.getString(R.string.digit_5);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -070098 case R.id.digit_6:
Justin Klaassene2711cb2015-05-28 11:13:17 -070099 return context.getString(R.string.digit_6);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700100 case R.id.digit_7:
Justin Klaassene2711cb2015-05-28 11:13:17 -0700101 return context.getString(R.string.digit_7);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700102 case R.id.digit_8:
Justin Klaassene2711cb2015-05-28 11:13:17 -0700103 return context.getString(R.string.digit_8);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700104 case R.id.digit_9:
Justin Klaassene2711cb2015-05-28 11:13:17 -0700105 return context.getString(R.string.digit_9);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700106 default:
Justin Klaassene2711cb2015-05-28 11:13:17 -0700107 return "";
Hans Boehm84614952014-11-25 18:46:17 -0800108 }
109 }
110
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700111 /**
112 * Does a button id correspond to a binary operator?
113 * Pure function.
114 */
Hans Boehm84614952014-11-25 18:46:17 -0800115 public static boolean isBinary(int id) {
116 switch(id) {
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700117 case R.id.op_pow:
118 case R.id.op_mul:
119 case R.id.op_div:
120 case R.id.op_add:
121 case R.id.op_sub:
122 return true;
123 default:
124 return false;
Hans Boehm84614952014-11-25 18:46:17 -0800125 }
126 }
127
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700128 /**
129 * Does a button id correspond to a suffix operator?
130 */
Hans Boehm84614952014-11-25 18:46:17 -0800131 public static boolean isSuffix(int id) {
Justin Klaassene2711cb2015-05-28 11:13:17 -0700132 switch (id) {
133 case R.id.op_fact:
134 case R.id.op_pct:
135 return true;
136 default:
137 return false;
138 }
Hans Boehm84614952014-11-25 18:46:17 -0800139 }
140
141 public static final int NOT_DIGIT = 10;
142
Hans Boehm08e8f322015-04-21 13:18:38 -0700143 public static final String ELLIPSIS = "\u2026";
144
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700145 /**
146 * Map key id to digit or NOT_DIGIT
147 * Pure function.
148 */
Hans Boehm84614952014-11-25 18:46:17 -0800149 public static int digVal(int id) {
150 switch (id) {
151 case R.id.digit_0:
152 return 0;
153 case R.id.digit_1:
154 return 1;
155 case R.id.digit_2:
156 return 2;
157 case R.id.digit_3:
158 return 3;
159 case R.id.digit_4:
160 return 4;
161 case R.id.digit_5:
162 return 5;
163 case R.id.digit_6:
164 return 6;
165 case R.id.digit_7:
166 return 7;
167 case R.id.digit_8:
168 return 8;
169 case R.id.digit_9:
170 return 9;
171 default:
172 return NOT_DIGIT;
173 }
174 }
175
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700176 /**
177 * Map digit to corresponding key. Inverse of above.
178 * Pure function.
179 */
Hans Boehm84614952014-11-25 18:46:17 -0800180 public static int keyForDigVal(int v) {
181 switch(v) {
182 case 0:
183 return R.id.digit_0;
184 case 1:
185 return R.id.digit_1;
186 case 2:
187 return R.id.digit_2;
188 case 3:
189 return R.id.digit_3;
190 case 4:
191 return R.id.digit_4;
192 case 5:
193 return R.id.digit_5;
194 case 6:
195 return R.id.digit_6;
196 case 7:
197 return R.id.digit_7;
198 case 8:
199 return R.id.digit_8;
200 case 9:
201 return R.id.digit_9;
202 default:
203 return View.NO_ID;
204 }
205 }
206
Hans Boehm425ed0a2015-05-19 18:27:31 -0700207 // The following two are only used for recognizing additional
208 // input characters from a physical keyboard. They are not used
209 // for output internationalization.
210 private static char mDecimalPt;
Hans Boehm84614952014-11-25 18:46:17 -0800211
Hans Boehmffda5282015-05-18 15:00:12 -0700212 private static char mPiChar;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700213
Hans Boehmffda5282015-05-18 15:00:12 -0700214 /**
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700215 * Character used as a placeholder for digits that are currently unknown in a result that
216 * is being computed. We initially generate blanks, and then use this as a replacement
217 * during final translation.
Hans Boehmffda5282015-05-18 15:00:12 -0700218 * <p/>
219 * Note: the character must correspond closely to the width of a digit,
220 * otherwise the UI will visibly shift once the computation is finished.
221 */
222 private static final char CHAR_DIGIT_UNKNOWN = '\u2007';
223
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700224 /**
225 * Map typed function name strings to corresponding button ids.
226 * We (now redundantly?) include both localized and English names.
227 */
Hans Boehmffda5282015-05-18 15:00:12 -0700228 private static HashMap<String, Integer> sKeyValForFun;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700229
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700230 /**
231 * Result string corresponding to a character in the calculator result.
232 * The string values in the map are expected to be one character long.
233 */
Hans Boehmffda5282015-05-18 15:00:12 -0700234 private static HashMap<Character, String> sOutputForResultChar;
Hans Boehm013969e2015-04-13 20:29:47 -0700235
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700236 /**
237 * Locale string corresponding to preceding map and character constants.
238 * We recompute the map if this is not the current locale.
239 */
Hans Boehmffda5282015-05-18 15:00:12 -0700240 private static String sLocaleForMaps = "none";
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700241
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700242 /**
243 * Activity to use for looking up buttons.
244 */
245 private static Activity mActivity;
Hans Boehm013969e2015-04-13 20:29:47 -0700246
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700247 /**
248 * Set acttivity used for looking up button labels.
249 * Call only from UI thread.
250 */
Hans Boehm013969e2015-04-13 20:29:47 -0700251 public static void setActivity(Activity a) {
252 mActivity = a;
253 }
254
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700255 /**
256 * Return the button id corresponding to the supplied character or return NO_ID.
257 * Called only by UI thread.
258 */
Hans Boehm013969e2015-04-13 20:29:47 -0700259 public static int keyForChar(char c) {
260 validateMaps();
Hans Boehm84614952014-11-25 18:46:17 -0800261 if (Character.isDigit(c)) {
262 int i = Character.digit(c, 10);
263 return KeyMaps.keyForDigVal(i);
264 }
Hans Boehm84614952014-11-25 18:46:17 -0800265 switch (c) {
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700266 case '.':
267 case ',':
268 return R.id.dec_point;
269 case '-':
270 return R.id.op_sub;
271 case '+':
272 return R.id.op_add;
273 case '*':
274 return R.id.op_mul;
275 case '/':
276 return R.id.op_div;
277 // We no longer localize function names, so they can't start with an 'e' or 'p'.
278 case 'e':
279 case 'E':
280 return R.id.const_e;
281 case 'p':
282 case 'P':
283 return R.id.const_pi;
284 case '^':
285 return R.id.op_pow;
286 case '!':
287 return R.id.op_fact;
Justin Klaassene2711cb2015-05-28 11:13:17 -0700288 case '%':
289 return R.id.op_pct;
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700290 case '(':
291 return R.id.lparen;
292 case ')':
293 return R.id.rparen;
294 default:
295 if (c == mDecimalPt) return R.id.dec_point;
296 if (c == mPiChar) return R.id.const_pi;
297 // pi is not translated, but it might be typable on a Greek keyboard,
298 // so we check ...
299 return View.NO_ID;
Hans Boehm84614952014-11-25 18:46:17 -0800300 }
301 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700302
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700303 /**
304 * Add information corresponding to the given button id to sKeyValForFun, to be used
305 * when mapping keyboard input to button ids.
306 */
Hans Boehm013969e2015-04-13 20:29:47 -0700307 static void addButtonToFunMap(int button_id) {
308 Button button = (Button)mActivity.findViewById(button_id);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700309 sKeyValForFun.put(button.getText().toString(), button_id);
310 }
311
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700312 /**
313 * Add information corresponding to the given button to sOutputForResultChar, to be used
314 * when translating numbers on output.
315 */
Hans Boehm013969e2015-04-13 20:29:47 -0700316 static void addButtonToOutputMap(char c, int button_id) {
317 Button button = (Button)mActivity.findViewById(button_id);
318 sOutputForResultChar.put(c, button.getText().toString());
319 }
320
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700321 // Ensure that the preceding map and character constants are
322 // initialized and correspond to the current locale.
323 // Called only by a single thread, namely the UI thread.
Hans Boehm013969e2015-04-13 20:29:47 -0700324 static void validateMaps() {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700325 Locale locale = Locale.getDefault();
326 String lname = locale.toString();
Hans Boehm013969e2015-04-13 20:29:47 -0700327 if (lname != sLocaleForMaps) {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700328 Log.v ("Calculator", "Setting local to: " + lname);
329 sKeyValForFun = new HashMap<String, Integer>();
330 sKeyValForFun.put("sin", R.id.fun_sin);
331 sKeyValForFun.put("cos", R.id.fun_cos);
332 sKeyValForFun.put("tan", R.id.fun_tan);
333 sKeyValForFun.put("arcsin", R.id.fun_arcsin);
334 sKeyValForFun.put("arccos", R.id.fun_arccos);
335 sKeyValForFun.put("arctan", R.id.fun_arctan);
336 sKeyValForFun.put("asin", R.id.fun_arcsin);
337 sKeyValForFun.put("acos", R.id.fun_arccos);
338 sKeyValForFun.put("atan", R.id.fun_arctan);
339 sKeyValForFun.put("ln", R.id.fun_ln);
340 sKeyValForFun.put("log", R.id.fun_log);
341 sKeyValForFun.put("sqrt", R.id.op_sqrt); // special treatment
Hans Boehm013969e2015-04-13 20:29:47 -0700342 addButtonToFunMap(R.id.fun_sin);
343 addButtonToFunMap(R.id.fun_cos);
344 addButtonToFunMap(R.id.fun_tan);
345 addButtonToFunMap(R.id.fun_arcsin);
346 addButtonToFunMap(R.id.fun_arccos);
347 addButtonToFunMap(R.id.fun_arctan);
348 addButtonToFunMap(R.id.fun_ln);
349 addButtonToFunMap(R.id.fun_log);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700350
351 // Set locale-dependent character "constants"
Hans Boehm013969e2015-04-13 20:29:47 -0700352 mDecimalPt =
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700353 DecimalFormatSymbols.getInstance().getDecimalSeparator();
Hans Boehm425ed0a2015-05-19 18:27:31 -0700354 // We recognize this in keyboard input, even if we use
355 // a different character.
Hans Boehm013969e2015-04-13 20:29:47 -0700356 Resources res = mActivity.getResources();
Hans Boehm425ed0a2015-05-19 18:27:31 -0700357 mPiChar = 0;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700358 String piString = res.getString(R.string.const_pi);
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700359 if (piString.length() == 1) {
360 mPiChar = piString.charAt(0);
361 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700362
Hans Boehm013969e2015-04-13 20:29:47 -0700363 sOutputForResultChar = new HashMap<Character, String>();
364 sOutputForResultChar.put('e', "E");
365 sOutputForResultChar.put('E', "E");
Hans Boehmffda5282015-05-18 15:00:12 -0700366 sOutputForResultChar.put(' ', String.valueOf(CHAR_DIGIT_UNKNOWN));
Hans Boehm08e8f322015-04-21 13:18:38 -0700367 sOutputForResultChar.put(ELLIPSIS.charAt(0), ELLIPSIS);
Hans Boehm013969e2015-04-13 20:29:47 -0700368 sOutputForResultChar.put('/', "/");
369 // Translate numbers for fraction display, but not
370 // the separating slash, which appears to be
371 // universal.
372 addButtonToOutputMap('-', R.id.op_sub);
Hans Boehm425ed0a2015-05-19 18:27:31 -0700373 addButtonToOutputMap('.', R.id.dec_point);
Hans Boehm013969e2015-04-13 20:29:47 -0700374 for (int i = 0; i <= 9; ++i) {
375 addButtonToOutputMap((char)('0' + i), keyForDigVal(i));
376 }
377
378 sLocaleForMaps = lname;
379
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700380 }
381 }
382
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700383 /**
384 * Return function button id for the substring of s starting at pos and ending with
385 * the next "(". Return NO_ID if there is none.
386 * We currently check for both (possibly localized) button labels, and standard
387 * English names. (They should currently be the same, and hence this is currently redundant.)
388 * Callable only from UI thread.
389 */
Hans Boehm013969e2015-04-13 20:29:47 -0700390 public static int funForString(String s, int pos) {
391 validateMaps();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700392 int parenPos = s.indexOf('(', pos);
393 if (parenPos != -1) {
394 String funString = s.substring(pos, parenPos);
395 Integer keyValue = sKeyValForFun.get(funString);
396 if (keyValue == null) return View.NO_ID;
397 return keyValue;
398 }
399 return View.NO_ID;
400 }
Hans Boehm013969e2015-04-13 20:29:47 -0700401
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700402 /**
403 * Return the localization of the string s representing a numeric answer.
404 * Callable only from UI thread.
405 */
Hans Boehm013969e2015-04-13 20:29:47 -0700406 public static String translateResult(String s) {
407 StringBuilder result = new StringBuilder();
408 int len = s.length();
409 validateMaps();
410 for (int i = 0; i < len; ++i) {
411 char c = s.charAt(i);
412 String translation = sOutputForResultChar.get(c);
413 if (translation == null) {
Hans Boehm0b3a9fd2015-05-22 11:36:26 -0700414 // Should not get here. Report if we do.
Hans Boehm013969e2015-04-13 20:29:47 -0700415 Log.v("Calculator", "Bad character:" + c);
416 result.append(String.valueOf(c));
417 } else {
418 result.append(translation);
419 }
420 }
421 return result.toString();
422 }
423
Hans Boehm84614952014-11-25 18:46:17 -0800424}