Modify cursor when adding/removing thousands separators (Bug #527669).
[gcalctool.git] / gcalctool / display.c
blobd09fccb0175d1d7ebb7218f856beb23d4a60a776
2 /* $Header$
4 * Copyright (c) 1987-2008 Sun Microsystems, Inc. All Rights Reserved.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2, or (at your option)
9 * any later version.
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
19 * 02111-1307, USA.
22 #include <stdio.h>
23 #include <string.h>
24 #include <assert.h>
26 #include "display.h"
28 #include "mp.h"
29 #include "mpmath.h"
30 #include "functions.h"
31 #include "ui.h"
33 static char digits[] = "0123456789ABCDEF";
35 static double max_fix[MAXBASES] = {
36 1.298074214e+33, /* Binary. */
37 2.037035976e+90, /* Octal. */
38 1.000000000e+100, /* Decimal */
39 2.582249878e+120 /* Hexadecimal. */
42 /* Add in the thousand separators characters if required and if we are
43 * currently in the decimal numeric base, use the "right" radix character.
46 /* Add in the thousand separators characters if required */
47 void
48 localize_expression(char *dest, const char *src, int dest_length, int *cursor)
50 GString *clean, *output;
51 const char *c, *d;
52 int digit_count = -1, read_cursor, new_cursor;
53 gboolean after_radix = FALSE;
55 /* Only modify if valid */
56 if (v->error || v->base != DEC) {
57 STRNCPY(dest, src, dest_length - 1);
58 return;
61 if (cursor) {
62 new_cursor = *cursor;
63 } else {
64 new_cursor = -1;
67 /* Remove separators if not supported */
68 clean = g_string_sized_new(strlen(src));
69 for (c = src, read_cursor = 1; *c; c++, read_cursor++) {
70 if (strncmp(c, v->tsep, strlen(v->tsep)) == 0) {
71 c += strlen(v->tsep) - 1;
72 if (new_cursor >= read_cursor) {
73 new_cursor--;
75 read_cursor--;
77 else {
78 g_string_append_c(clean, *c);
82 if (!v->show_tsep) {
83 STRNCPY(dest, clean->str, dest_length - 1);
84 g_string_free(clean, TRUE);
85 return;
88 /* Scan expression looking for numbers and inserting separators */
89 output = g_string_sized_new(dest_length);
90 for (c = clean->str, read_cursor = 1; *c; c++, read_cursor++) {
91 /* Insert separators between digits */
92 if (*c >= '0' && *c <= '9') {
93 /* Read ahead to find the number of digits */
94 if (digit_count < 0) {
95 digit_count = 1;
96 for (d = c + 1; *d >= '0' && *d <= '9'; d++) {
97 digit_count++;
101 g_string_append_c(output, *c);
103 /* Insert separator after nth digit */
104 if (!after_radix && digit_count > 1 && digit_count % v->tsep_count == 1) {
105 g_string_append(output, v->tsep);
106 if (new_cursor > read_cursor) {
107 new_cursor++;
109 read_cursor++;
111 digit_count--;
113 /* Ignore digits after the radix */
114 else if (strncmp(c, v->radix, strlen(v->radix)) == 0) {
115 digit_count = -1;
116 after_radix = TRUE;
117 c += strlen(v->radix) - 1;
118 g_string_append(output, v->radix);
120 /* Reset when encountering other characters (e.g. '+') */
121 else {
122 digit_count = -1;
123 after_radix = FALSE;
124 g_string_append_c(output, *c);
128 STRNCPY(dest, output->str, dest_length - 1);
129 g_string_free(output, TRUE);
130 g_string_free(clean, TRUE);
132 if (cursor != NULL && *cursor != -1) {
133 *cursor = new_cursor;
138 static int
139 char_val(char chr)
141 if (chr >= '0' && chr <= '9') {
142 return(chr - '0');
143 } else if (chr >= 'a' && chr <= 'f') {
144 return(chr - 'a' + 10);
145 } else if (chr >= 'A' && chr <= 'F') {
146 return(chr - 'A' + 10);
147 } else {
148 return(-1);
153 void
154 display_clear(int initialise)
156 v->ltr.pointed = 0;
157 v->ltr.toclear = 1;
158 do_zero(v->MPdisp_val);
159 display_set_number(v->MPdisp_val);
161 if (initialise == TRUE) {
162 v->ltr.noparens = 0;
163 ui_set_hyperbolic_state(FALSE); /* Also clears v->hyperbolic. */
164 ui_set_inverse_state(FALSE); /* Also clears v->inverse. */
169 void
170 display_reset()
172 v->error = 0; /* Currently no display error. */
173 v->ltr.cur_op = -1; /* No arithmetic operator defined yet. */
174 v->ltr.old_cal_value = -1;
175 do_zero(v->MPresult); /* No previous result yet. */
176 do_zero(v->MPdisp_val);
177 do_zero(v->MPlast_input);
179 v->ltr.new_input = 1; /* Value zero is on calculator display */
181 exp_clear();
185 /* Convert MP number to fixed number string in the given base to the
186 * maximum number of digits specified.
189 void
190 make_fixed(char *target, int target_len, int *MPnumber, int base, int cmax, int toclear)
192 char half[MAXLINE], *optr;
193 int MP1base[MP_SIZE], MP1[MP_SIZE], MP2[MP_SIZE], MPval[MP_SIZE];
194 int ndig; /* Total number of digits to generate. */
195 int ddig; /* Number of digits to left of decimal sep. */
196 int dval, n, i;
198 optr = target;
199 mpabs(MPnumber, MPval);
200 do_zero(MP1);
201 if (mplt(MPnumber, MP1)) {
202 *optr++ = '-';
205 mpcim(&basevals[base], MP1base);
207 mppwr(MP1base, &v->accuracy, MP1);
208 /* FIXME: string const. if MPstr_to_num can get it */
209 SNPRINTF(half, MAXLINE, "0.5");
210 MPstr_to_num(half, DEC, MP2);
211 mpdiv(MP2, MP1, MP1);
212 mpadd(MPval, MP1, MPval);
214 n = 1;
215 mpcim(&n, MP2);
216 if (mplt(MPval, MP2)) {
217 ddig = 0;
218 *optr++ = '0';
219 cmax--;
220 } else {
221 for (ddig = 0; mpge(MPval, MP2); ddig++) {
222 mpdiv(MPval, MP1base, MPval);
226 ndig = MIN(ddig + v->accuracy, --cmax);
228 while (ndig-- > 0) {
229 if (ddig-- == 0) {
230 for (i = 0; i < strlen(v->radix); i++)
231 *optr++ = v->radix[i];
233 mpmul(MPval, MP1base, MPval);
234 mpcmi(MPval, &dval);
236 if (dval > basevals[base]-1) {
237 dval = basevals[base]-1;
240 *optr++ = digits[dval];
241 dval = -dval;
242 mpaddi(MPval, &dval, MPval);
244 *optr++ = '\0';
245 if (toclear == TRUE) {
246 v->ltr.toclear = 1;
248 v->ltr.pointed = 0;
250 /* Strip off trailing zeroes */
251 if (!v->show_zeroes) {
252 for (i = strlen(target) - 1; i > 1 && target[i] == '0'; i--) {
253 target[i] = '\0';
256 /* If no fractional part discard radix */
257 if (strlen(target) >= strlen(v->radix) && strcmp(target + strlen(target) - strlen(v->radix), v->radix) == 0) {
258 target[strlen(target) - strlen(v->radix)] = '\0';
264 /* Convert engineering or scientific number in the given base. */
266 void
267 make_eng_sci(char *target, int target_len, int *MPnumber, int base)
269 char half[MAXLINE], fixed[MAX_DIGITS], *optr;
270 int MP1[MP_SIZE], MPatmp[MP_SIZE], MPval[MP_SIZE];
271 int MP1base[MP_SIZE], MP3base[MP_SIZE], MP10base[MP_SIZE];
272 int i, dval, len, n;
273 int MPmant[MP_SIZE]; /* Mantissa. */
274 int ddig; /* Number of digits in exponent. */
275 int eng = 0; /* Set if this is an engineering number. */
276 int exp = 0; /* Exponent */
278 if (v->dtype == ENG) {
279 eng = 1;
281 optr = target;
282 mpabs(MPnumber, MPval);
283 do_zero(MP1);
284 if (mplt(MPnumber, MP1)) {
285 *optr++ = '-';
287 mpstr(MPval, MPmant);
289 mpcim(&basevals[base], MP1base);
290 n = 3;
291 mppwr(MP1base, &n, MP3base);
293 n = 10;
294 mppwr(MP1base, &n, MP10base);
296 n = 1;
297 mpcim(&n, MP1);
298 mpdiv(MP1, MP10base, MPatmp);
300 do_zero(MP1);
301 if (!mpeq(MPmant, MP1)) {
302 while (!eng && mpge(MPmant, MP10base)) {
303 exp += 10;
304 mpmul(MPmant, MPatmp, MPmant);
307 while ((!eng && mpge(MPmant, MP1base)) ||
308 (eng && (mpge(MPmant, MP3base) || exp % 3 != 0))) {
309 exp += 1;
310 mpdiv(MPmant, MP1base, MPmant);
313 while (!eng && mplt(MPmant, MPatmp)) {
314 exp -= 10;
315 mpmul(MPmant, MP10base, MPmant);
318 n = 1;
319 mpcim(&n, MP1);
320 while (mplt(MPmant, MP1) || (eng && exp % 3 != 0)) {
321 exp -= 1;
322 mpmul(MPmant, MP1base, MPmant);
326 make_fixed(fixed, MAX_DIGITS, MPmant, base, MAX_DIGITS-6, TRUE);
327 len = strlen(fixed);
328 for (i = 0; i < len; i++) {
329 *optr++ = fixed[i];
332 *optr++ = 'e';
334 if (exp < 0) {
335 exp = -exp;
336 *optr++ = '-';
337 } else {
338 *optr++ = '+';
341 SNPRINTF(half, MAXLINE, "0.5");
342 MPstr_to_num(half, DEC, MP1);
343 mpaddi(MP1, &exp, MPval);
344 n = 1;
345 mpcim(&n, MP1);
346 for (ddig = 0; mpge(MPval, MP1); ddig++) {
347 mpdiv(MPval, MP1base, MPval);
350 if (ddig == 0) {
351 *optr++ = '0';
354 while (ddig-- > 0) {
355 mpmul(MPval, MP1base, MPval);
356 mpcmi(MPval, &dval);
357 *optr++ = digits[dval];
358 dval = -dval;
359 mpaddi(MPval, &dval, MPval);
361 *optr++ = '\0';
362 v->ltr.toclear = 1;
363 v->ltr.pointed = 0;
367 /* Convert MP number to character string in the given base. */
369 void
370 make_number(char *target, int target_len, int *MPnumber, int base, int ignoreError)
372 double number, val;
374 /* NOTE: make_number can currently set v->error when converting to a double.
375 * This is to provide the same look&feel as V3 even though gcalctool
376 * now does internal arithmetic to "infinite" precision.
378 * XXX: Needs to be improved. Shouldn't need to convert to a double in
379 * order to do these tests.
382 mpcmd(MPnumber, &number);
383 val = fabs(number);
384 if (v->error && !ignoreError) {
385 STRNCPY(target, _("Error"), target_len - 1);
386 return;
388 if ((v->dtype == ENG) ||
389 (v->dtype == SCI) ||
390 (v->dtype == FIX && val != 0.0 && (val > max_fix[base]))) {
391 make_eng_sci(target, target_len, MPnumber, base);
392 } else {
393 make_fixed(target, target_len, MPnumber, base, MAX_DIGITS, TRUE);
398 /* Convert string into an MP number, in the given base
401 void
402 MPstr_to_num(char *str, enum base_type base, int *MPval)
404 char *optr;
405 int MP1[MP_SIZE], MP2[MP_SIZE], MPbase[MP_SIZE];
406 int i, inum;
407 int exp = 0;
408 int exp_sign = 1;
409 int negate = 0;
410 char *lnp = ui_get_localized_numeric_point();
411 assert(lnp);
413 do_zero(MPval);
414 mpcim(&basevals[(int) base], MPbase);
416 optr = str;
418 /* Remove any initial spaces or tabs. */
419 while (*optr == ' ' || *optr == '\t') {
420 optr++;
423 /* Check if this is a negative number. */
424 if (*optr == '-') {
425 negate = 1;
426 optr++;
429 while ((inum = char_val(*optr)) >= 0) {
430 mpmul(MPval, MPbase, MPval);
431 mpaddi(MPval, &inum, MPval);
432 optr++;
435 if (*optr == '.' || *optr == *lnp) {
436 optr++;
437 for (i = 1; (inum = char_val(*optr)) >= 0; i++) {
438 mppwr(MPbase, &i, MP1);
439 mpcim(&inum, MP2);
440 mpdiv(MP2, MP1, MP1);
441 mpadd(MPval, MP1, MPval);
442 optr++;
446 while (*optr == ' ') {
447 optr++;
450 if (*optr != '\0') {
451 if (*optr == '-') {
452 exp_sign = -1;
455 while ((inum = char_val(*++optr)) >= 0) {
456 exp = exp * basevals[(int) base] + inum;
459 exp *= exp_sign;
461 if (v->ltr.key_exp) {
462 mppwr(MPbase, &exp, MP1);
463 mpmul(MPval, MP1, MPval);
466 if (negate == 1) {
467 mpneg(MPval, MPval);
472 /* Append the latest parenthesis char to the display item. */
474 void
475 paren_disp(int key)
477 int n;
478 char *text;
480 /* If the character is a Delete, clear the whole line, and exit parenthesis
481 * processing.
483 * If the character is a Back Space, remove the last character. If the last
484 * character was a left parenthesis, decrement the parentheses count. If
485 * the parentheses count is zero, exit parenthesis processing.
487 * Otherwise just append the character.
490 n = strlen(v->display);
491 text = buttons[key].symname;
492 switch (key) {
493 case -1:
494 case KEY_CLEAR:
495 v->ltr.noparens = 0;
496 v->ltr.cur_op = -1;
497 do_zero(v->MPdisp_val);
498 display_set_number(v->MPdisp_val);
499 return;
500 case KEY_BACKSPACE:
501 if (!n) {
502 return;
505 if (v->display[n-1] == ')') {
506 v->ltr.noparens++;
507 } else if (v->display[n-1] == '(') {
508 v->ltr.noparens--;
509 if (!v->ltr.noparens) {
510 v->ltr.cur_op = -1;
511 display_set_number(v->MPdisp_val);
512 return;
514 } else if (v->display[n-1] == ')') v->ltr.noparens++;
515 v->display[n-1] = '\0';
516 break;
518 case KEY_START_BLOCK:
520 /* If this is the first left parenthesis being displayed and there is no
521 * current arithmetic operand, then the current display is initially cleared
522 * to avoid the confusion of showing something like "0(".
525 if (v->ltr.noparens == 1 && v->ltr.cur_op == -1) {
526 n = 0;
527 v->display[0] = '\0';
529 text = "(";
530 break;
532 case KEY_END_BLOCK:
533 text = ")";
534 break;
537 if (text) {
538 SNPRINTF(v->display+n, MAXLINE-n, "%s", text);
541 n = (n < MAX_DIGITS) ? 0 : n - MAX_DIGITS;
542 ui_set_display(&v->display[n], -1);
545 void
546 display_set_number(int *MPval)
548 if (!v->error) {
549 make_number(v->display, MAXLINE, MPval, v->base, FALSE);
550 ui_set_display(v->display, -1);
554 void
555 display_set_string(char *value)
557 if(value != v->display)
558 STRNCPY(value, v->display, MAX_DIGITS - 1);
559 ui_set_display(v->display, -1);
562 /* In arithmetic precedence mode this routine should be called to redraw
563 * the display.
565 void
566 display_refresh(int cursor)
568 int i, MP_reg[MP_SIZE];
569 char localized[MAX_LOCALIZED], *str, reg[3], *t;
570 struct exprm_state *e;
571 char x[MAX_LOCALIZED], xx[MAX_LOCALIZED], ans[MAX_LOCALIZED];
573 switch (v->syntax) {
574 case NPA:
575 display_set_number(v->MPdisp_val);
576 break;
578 case EXPRS:
579 e = get_state();
580 if (e->expression[0] == '\0') {
581 do_zero(MP_reg);
582 make_number(x, MAX_LOCALIZED, MP_reg, v->base, FALSE);
583 str = x;
584 } else {
585 str = gc_strdup(e->expression);
588 /* Substitute answer register */
589 make_number(ans, MAX_LOCALIZED, e->ans, v->base, TRUE);
590 localize_expression(localized, ans, MAX_LOCALIZED, &cursor);
591 str = str_replace(str, "Ans", localized);
593 /* Replace registers with values. */
594 for (i = 0; i < 10; i++) {
595 SNPRINTF(reg, 3, "R%d", i);
596 do_rcl_reg(i, MP_reg);
597 make_number(xx, MAX_LOCALIZED, MP_reg, v->base, FALSE);
598 t = str_replace(str, reg, xx);
599 free(str);
600 str = t;
603 ui_set_display(str, cursor);
604 free(str);
605 break;
607 default:
608 assert(0);
612 gboolean display_is_result(void)
614 struct exprm_state *e;
616 switch (v->syntax) {
617 case NPA:
618 if (v->ltr.old_cal_value < 0 ||
619 v->ltr.old_cal_value == KEY_CALCULATE) {
620 return TRUE;
622 break;
624 case EXPRS:
625 e = get_state();
626 if (strcmp(e->expression, "Ans") == 0) {
627 return TRUE;
629 break;
631 default:
632 assert(FALSE);
635 return FALSE;