Fix errors from make distcheck
[gcalctool.git] / src / display.c
blob1dbbc1d6d965ef48362a37ba54570716084badf5
2 /* $Header$
4 * Copyright (c) 1987-2008 Sun Microsystems, Inc. All Rights Reserved.
5 * Copyright (c) 2008 Robert Ancell
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2, or (at your option)
10 * any later version.
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20 * 02111-1307, USA.
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <string.h>
26 #include <math.h>
27 #include <errno.h>
28 #include <assert.h>
30 #include "display.h"
32 #include "get.h"
33 #include "mp.h"
34 #include "functions.h"
35 #include "ui.h"
36 #include "mp-equation.h" // For mp_equation_parse()
37 #include "register.h"
39 static const char *display_types[] = { "ENG", "FIX", "SCI", NULL };
41 static GCDisplayState *
42 get_state(GCDisplay *display)
44 return &(display->h.e[display->h.current]);
47 static gboolean
48 exp_has_postfix(char *str, char *postfix)
50 int len, plen;
52 if (!str) {
53 return FALSE;
56 assert(postfix);
58 len = strlen(str);
59 plen = strlen(postfix);
61 if (plen > len) {
62 return FALSE;
65 return strcasecmp(str + len - plen, postfix) == 0;
68 static char *
69 str_replace(char *str, char *from, char *to)
71 char output[MAX_DISPLAY];
72 int offset = 0;
73 char *c;
74 int flen = strlen(from);
75 int tlen = strlen(to);
77 for (c = str; *c && offset < MAX_DISPLAY - 1; c++, offset++) {
78 if (strncasecmp(from, c, flen) == 0) {
79 SNPRINTF(output + offset, MAX_DISPLAY - offset, "%s", to);
80 c += flen - 1;
81 offset += tlen - 1;
82 } else {
83 output[offset] = *c;
87 if (offset >= MAX_DISPLAY)
88 offset = MAX_DISPLAY - 1;
89 output[offset] = '\0';
91 free(str);
93 return strdup(output);
96 /* Add in the thousand separators characters if required and if we are
97 * currently in the decimal numeric base, use the "right" radix character.
100 /* Add in the thousand separators characters if required */
101 static void
102 localize_expression(char *dest, const char *src, int dest_length, int *cursor)
104 GString *output;
105 const char *c, *d;
106 int digit_count = -1, read_cursor, new_cursor;
107 gboolean after_radix = FALSE;
109 if (cursor) {
110 new_cursor = *cursor;
111 } else {
112 new_cursor = -1;
115 /* Scan expression looking for numbers and inserting separators */
116 output = g_string_sized_new(dest_length);
117 for (c = src, read_cursor = 1; *c; c++, read_cursor++) {
118 /* Insert separators between digits */
119 if (*c >= '0' && *c <= '9') {
120 /* Read ahead to find the number of digits */
121 if (digit_count < 0) {
122 digit_count = 1;
123 for (d = c + 1; *d >= '0' && *d <= '9'; d++) {
124 digit_count++;
128 g_string_append_c(output, *c);
130 /* Insert separator after nth digit */
131 if (v->display.show_tsep && v->base == DEC &&
132 !after_radix && digit_count > 1 && digit_count % v->tsep_count == 1) {
133 g_string_append(output, v->tsep);
134 if (new_cursor > read_cursor) {
135 new_cursor++;
137 read_cursor++;
139 digit_count--;
141 /* Ignore digits after the radix */
142 else if (*c == '.') {
143 digit_count = -1;
144 after_radix = TRUE;
145 g_string_append(output, v->radix);
146 // FIXME: Handle cursor if radix is more than one character?
148 /* Reset when encountering other characters (e.g. '+') */
149 else {
150 digit_count = -1;
151 after_radix = FALSE;
152 g_string_append_c(output, *c);
156 STRNCPY(dest, output->str, dest_length - 1);
157 g_string_free(output, TRUE);
159 if (cursor != NULL && *cursor != -1) {
160 *cursor = new_cursor;
165 void
166 display_clear(GCDisplay *display)
168 v->error = 0;
169 display_set_string(display, "", -1);
173 static const char *
174 display_get_text(GCDisplay *display)
176 return get_state(display)->expression;
180 gboolean display_get_integer(GCDisplay *display, gint64 *value)
182 const char *text;
183 char buf[MAX_DISPLAY];
184 gchar *endptr;
185 guint bases[] = {2, 8, 10, 16};
187 text = display_get_text(display);
188 if (text[0] == '\0') {
189 text = "0";
191 else if (display_is_result(display)) {
192 display_make_number(display, buf, MAX_DISPLAY, display_get_answer(display), v->base, FALSE);
193 text = buf;
196 *value = g_ascii_strtoll(text, &endptr, bases[v->base]);
197 if(*endptr != '\0' || ((*value == G_MAXINT64 || *value == G_MININT64) && errno == ERANGE))
198 return FALSE;
199 return TRUE;
203 gboolean display_get_unsigned_integer(GCDisplay *display, guint64 *value)
205 const char *text;
206 char buf[MAX_DISPLAY];
207 gchar *endptr;
208 guint bases[] = {2, 8, 10, 16};
210 text = display_get_text(display);
211 if (text[0] == '\0') {
212 text = "0";
214 else if (display_is_result(display)) {
215 display_make_number(display, buf, MAX_DISPLAY, display_get_answer(display), v->base, FALSE);
216 text = buf;
219 /* strtoull() treats the string like a 2's complement number which is not what we want */
220 if(text[0] == '-')
221 return FALSE;
223 *value = g_ascii_strtoull(text, &endptr, bases[v->base]);
224 if(*endptr != '\0' || (*value == G_MAXUINT64 && errno == ERANGE))
225 return FALSE;
226 return TRUE;
230 MPNumber *display_get_answer(GCDisplay *display)
232 return &get_state(display)->ans;
237 display_get_cursor(GCDisplay *display)
239 return get_state(display)->cursor;
243 void
244 display_set_number(GCDisplay *display, const MPNumber *MPval)
246 char text[MAX_DISPLAY];
247 display_make_number(display, text, MAX_DISPLAY, MPval, v->base, FALSE);
248 display_set_string(display, text, -1);
252 void
253 display_set_answer(GCDisplay *display)
255 display_set_string(display, "Ans", -1);
259 static void
260 display_refresh(GCDisplay *display)
262 int i;
263 MPNumber MP_reg;
264 char localized[MAX_LOCALIZED], temp[MAX_LOCALIZED], *str, reg[3];
265 GCDisplayState *e;
266 int cursor = display_get_cursor(display);
268 e = get_state(display);
269 if (display_is_empty(display)) {
270 mp_set_from_integer(0, &MP_reg);
271 display_make_number(display, temp, MAX_LOCALIZED, &MP_reg, v->base, FALSE);
272 str = strdup(temp);
273 } else {
274 str = strdup(e->expression);
277 /* Substitute answer register */
278 display_make_number(display, temp, MAX_LOCALIZED, &e->ans, v->base, TRUE);
279 str = str_replace(str, "Ans", temp);
281 /* Replace registers with values. */
282 for (i = 0; i < 10; i++) {
283 SNPRINTF(reg, 3, "R%d", i);
284 register_get(i, &MP_reg);
285 display_make_number(display, temp, MAX_LOCALIZED, &MP_reg, v->base, FALSE);
286 str = str_replace(str, reg, temp);
289 localize_expression(localized, str, MAX_LOCALIZED, &cursor);
290 ui_set_display(localized, cursor);
291 free(str);
295 void
296 display_set_string(GCDisplay *display, const char *value, int cursor)
298 GCDisplayState *e;
300 e = get_state(display);
301 free(e->expression);
302 e->expression = strdup(value);
303 e->cursor = cursor;
305 display_refresh(display);
308 void
309 display_set_cursor(GCDisplay *display, int cursor)
311 GCDisplayState *e;
313 e = get_state(display);
314 e->cursor = cursor;
315 display_refresh(display);
318 void
319 display_set_error(GCDisplay *display, const char *message)
321 ui_set_statusbar(message, "gtk-dialog-error");
325 static void
326 copy_state(GCDisplayState *dst, GCDisplayState *src)
328 memcpy(dst, src, sizeof(GCDisplayState));
329 dst->expression = strdup(src->expression);
333 static void
334 update_undo_redo_button_sensitivity(GCDisplay *display)
336 int undo = 0;
337 int redo = 0;
339 if (display->h.current != display->h.end) {
340 redo = 1;
343 if (display->h.current != display->h.begin) {
344 undo = 1;
347 ui_set_undo_enabled(undo, redo);
351 void display_clear_stack(GCDisplay *display)
353 int i = display->h.begin;
354 while (i != display->h.end) {
355 if (i != display->h.current) {
356 free(display->h.e[i].expression);
357 display->h.e[i].expression = NULL;
359 i = ((i + 1) % UNDO_HISTORY_LENGTH);
361 display->h.begin = display->h.end = display->h.current;
362 update_undo_redo_button_sensitivity(display);
366 void display_push(GCDisplay *display)
368 int c;
370 if (display->h.current != display->h.end) {
371 int i = display->h.current;
373 do {
374 i = ((i + 1) % UNDO_HISTORY_LENGTH);
375 free(display->h.e[i].expression);
376 display->h.e[i].expression = strdup("Ans");
377 } while (i != display->h.end);
380 display->h.end = display->h.current;
382 c = display->h.current;
383 display->h.end = display->h.current = ((display->h.current + 1) % UNDO_HISTORY_LENGTH);
384 if (display->h.current == display->h.begin) {
385 free(display->h.e[display->h.begin].expression);
386 display->h.e[display->h.begin].expression = NULL;
387 display->h.begin = ((display->h.begin + 1) % UNDO_HISTORY_LENGTH);
390 copy_state(&(display->h.e[display->h.current]), &(display->h.e[c]));
391 update_undo_redo_button_sensitivity(display);
395 void display_pop(GCDisplay *display)
397 if (display->h.current != display->h.begin) {
398 display->h.current = ((display->h.current - 1) % UNDO_HISTORY_LENGTH);
399 ui_set_statusbar("", "");
400 } else {
401 ui_set_statusbar(_("No undo history"), "gtk-dialog-warning");
403 update_undo_redo_button_sensitivity(display);
405 display_refresh(display);
409 void
410 display_unpop(GCDisplay *display)
412 if (display->h.current != display->h.end) {
413 display->h.current = ((display->h.current + 1) % UNDO_HISTORY_LENGTH);
414 ui_set_statusbar("", "");
415 } else {
416 ui_set_statusbar(_("No redo steps"), "gtk-dialog-warning");
418 update_undo_redo_button_sensitivity(display);
419 get_state(display)->cursor = -1;
420 display_refresh(display);
424 gboolean
425 display_is_undo_step(GCDisplay *display)
427 return(display->h.current != display->h.begin);
430 void
431 display_insert(GCDisplay *display, int cursor, const char *text)
433 char buf[MAX_DISPLAY], *currentText;
435 if (cursor < 0) {
436 SNPRINTF(buf, MAX_DISPLAY, "%s%s", display_get_text(display), text);
437 } else {
438 currentText = ui_get_display();
439 SNPRINTF(buf, MAX_DISPLAY, "%.*s%s%s", cursor, currentText, text, currentText + cursor);
440 cursor += strlen(text);
442 display_set_string(display, buf, cursor);
445 void
446 display_insert_at_cursor(GCDisplay *display, const char *text)
448 display_insert(display, display_get_cursor(display), text);
451 void
452 display_insert_number(GCDisplay *display, int cursor, const MPNumber *value)
454 char text[MAX_DISPLAY];
455 display_make_number(display, text, MAX_DISPLAY, value, v->base, FALSE);
456 display_insert(display, cursor, text);
459 void
460 display_insert_number_at_cursor(GCDisplay *display, const MPNumber *value)
462 display_insert_number(display, display_get_cursor(display), value);
466 void
467 display_backspace(GCDisplay *display)
469 char buf[MAX_DISPLAY] = "", buf2[MAX_DISPLAY], *t;
470 GCDisplayState *e = get_state(display);
471 int i, cursor;
472 MPNumber MP_reg;
474 cursor = display_get_cursor(display);
476 /* If cursor is at end of the line then delete the last character preserving accuracy */
477 if (cursor < 0) {
478 if (exp_has_postfix(e->expression, "Ans")) {
479 display_make_number(display, buf, MAX_DISPLAY, &e->ans, v->base, FALSE);
480 e->expression = str_replace(e->expression, "Ans", buf);
481 } else {
482 for (i = 0; i < 10; i++) {
483 SNPRINTF(buf, MAX_DISPLAY, "R%d", i);
484 if (exp_has_postfix(e->expression, buf)) {
485 register_get(i, &MP_reg);
486 display_make_number(display, buf2, MAX_DISPLAY, &MP_reg, v->base, FALSE);
487 /* Remove "Rx" postfix and replace with backspaced number */
488 SNPRINTF(buf, MAX_DISPLAY, "%.*s%s", strlen(e->expression) - 2, e->expression - 3, buf2);
489 display_set_string(display, buf, cursor - 1);
490 return;
495 SNPRINTF(buf, MAX_DISPLAY, "%.*s", strlen(e->expression) - 1, e->expression);
496 } else if (cursor > 0) {
497 t = ui_get_display();
498 SNPRINTF(buf, MAX_DISPLAY, "%.*s%s", cursor - 1, t, t + cursor);
499 } else {
500 return; /* At the start of the line */
503 display_set_string(display, buf, cursor - 1);
506 void
507 display_delete(GCDisplay *display)
509 char buf[MAX_DISPLAY] = "", *text;
510 int cursor = display_get_cursor(display);
512 if (cursor >= 0) {
513 text = ui_get_display();
514 SNPRINTF(buf, MAX_DISPLAY, "%.*s%s", cursor, text, text + cursor + 1);
515 display_set_string(display, buf, cursor);
519 void
520 display_surround(GCDisplay *display, const char *prefix, const char *suffix)
522 char buffer[MAX_DISPLAY];
524 SNPRINTF(buffer, MAX_DISPLAY, "%s%s%s", prefix, display_get_text(display), suffix);
525 display_set_string(display, buffer, -1);
529 gboolean
530 display_is_empty(GCDisplay *display)
532 return strcmp(display_get_text(display), "") == 0;
535 gboolean
536 display_is_result(GCDisplay *display)
538 if (strcmp(display_get_text(display), "Ans") == 0)
539 return TRUE;
541 return FALSE;
544 gboolean
545 display_is_usable_number(GCDisplay *display, MPNumber *MPnum)
547 if (display_is_empty(display)) {
548 return mp_equation_parse("0", MPnum);
549 } else {
550 return mp_equation_parse(display_get_text(display), MPnum);
555 void
556 display_init(GCDisplay *display)
558 int i;
560 memset(display, 0, sizeof(GCDisplay));
562 display->base = 10;
564 if (get_boolean_resource(R_ZEROES, &i))
565 display->show_zeroes = i;
566 else
567 display->show_zeroes = FALSE;
569 if (get_boolean_resource(R_TSEP, &i))
570 display->show_tsep = i;
571 else
572 display->show_tsep = FALSE;
574 if (get_enumerated_resource(R_DISPLAY, display_types, &i))
575 display->format = (DisplayFormat) i;
576 else
577 display->format = FIX;
579 for (i = 0; i < UNDO_HISTORY_LENGTH; i++)
580 display->h.e[i].expression = strdup("");
584 void display_set_accuracy(GCDisplay *display, int accuracy)
586 set_int_resource(R_ACCURACY, accuracy);
587 get_state(display)->cursor = -1;
588 display_refresh(display);
592 void display_set_show_thousands_separator(GCDisplay *display, gboolean visible)
594 display->show_tsep = visible;
595 set_boolean_resource(R_TSEP, visible);
596 display_set_cursor(display, -1);
597 display_refresh(display);
601 void display_set_show_trailing_zeroes(GCDisplay *display, gboolean visible)
603 display->show_zeroes = visible;
604 set_boolean_resource(R_ZEROES, visible);
605 get_state(display)->cursor = -1;
606 display_refresh(display);
610 void display_set_base(GCDisplay *display, int base)
612 display->base = base;
613 get_state(display)->cursor = -1;
614 display_refresh(display);
618 void display_set_format(GCDisplay *display, DisplayFormat type)
620 v->display.format = type;
621 set_enumerated_resource(R_DISPLAY, display_types, (int) type);
622 get_state(display)->cursor = -1;
623 display_refresh(display);
628 display_solve(GCDisplay *display, MPNumber *result)
630 const char *text;
631 int errorCode;
633 text = display_get_text(display);
634 errorCode = mp_equation_parse(text, result);
636 return errorCode;
640 /* Convert engineering or scientific number in the given base. */
641 void
642 make_eng_sci(GCDisplay *display, char *target, int target_len, const MPNumber *MPnumber, int base)
644 static char digits[] = "0123456789ABCDEF";
645 char fixed[MAX_DIGITS], *optr;
646 MPNumber MP1, MPatmp, MPval;
647 MPNumber MP1base, MP3base, MP10base;
648 int i, dval, len;
649 MPNumber MPmant; /* Mantissa. */
650 int ddig; /* Number of digits in exponent. */
651 int eng = 0; /* Set if this is an engineering number. */
652 int exp = 0; /* Exponent */
654 if (display->format == ENG) {
655 eng = 1;
657 optr = target;
658 mp_abs(MPnumber, &MPval);
659 mp_set_from_integer(0, &MP1);
660 if (mp_is_less_than(MPnumber, &MP1)) {
661 *optr++ = '-';
663 mp_set_from_mp(&MPval, &MPmant);
665 mp_set_from_integer(basevals[base], &MP1base);
666 mp_pwr_integer(&MP1base, 3, &MP3base);
668 mp_pwr_integer(&MP1base, 10, &MP10base);
670 mp_set_from_integer(1, &MP1);
671 mp_divide(&MP1, &MP10base, &MPatmp);
673 mp_set_from_integer(0, &MP1);
674 if (!mp_is_equal(&MPmant, &MP1)) {
675 while (!eng && mp_is_greater_equal(&MPmant, &MP10base)) {
676 exp += 10;
677 mp_multiply(&MPmant, &MPatmp, &MPmant);
680 while ((!eng && mp_is_greater_equal(&MPmant, &MP1base)) ||
681 (eng && (mp_is_greater_equal(&MPmant, &MP3base) || exp % 3 != 0))) {
682 exp += 1;
683 mp_divide(&MPmant, &MP1base, &MPmant);
686 while (!eng && mp_is_less_than(&MPmant, &MPatmp)) {
687 exp -= 10;
688 mp_multiply(&MPmant, &MP10base, &MPmant);
691 mp_set_from_integer(1, &MP1);
692 while (mp_is_less_than(&MPmant, &MP1) || (eng && exp % 3 != 0)) {
693 exp -= 1;
694 mp_multiply(&MPmant, &MP1base, &MPmant);
698 mp_cast_to_string(&MPmant, basevals[base], v->accuracy, !v->display.show_zeroes, fixed, MAX_DIGITS);
699 len = strlen(fixed);
700 for (i = 0; i < len; i++) {
701 *optr++ = fixed[i];
704 *optr++ = 'e';
706 if (exp < 0) {
707 exp = -exp;
708 *optr++ = '-';
709 } else {
710 *optr++ = '+';
713 mp_set_from_string("0.5", 10, &MP1);
714 mp_add_integer(&MP1, exp, &MPval);
715 mp_set_from_integer(1, &MP1);
716 for (ddig = 0; mp_is_greater_equal(&MPval, &MP1); ddig++) {
717 mp_divide(&MPval, &MP1base, &MPval);
720 if (ddig == 0) {
721 *optr++ = '0';
724 while (ddig-- > 0) {
725 mp_multiply(&MPval, &MP1base, &MPval);
726 dval = mp_cast_to_int(&MPval);
727 *optr++ = digits[dval];
728 dval = -dval;
729 mp_add_integer(&MPval, dval, &MPval);
731 *optr++ = '\0';
735 /* Convert MP number to character string in the given base. */
736 void
737 display_make_number(GCDisplay *display, char *target, int target_len, const MPNumber *MPnumber, int base, int ignoreError)
739 static double max_fix[MAXBASES] = {
740 1.298074214e+33, /* Binary. */
741 2.037035976e+90, /* Octal. */
742 1.000000000e+100, /* Decimal */
743 2.582249878e+120 /* Hexadecimal. */
746 double val;
748 /* NOTE: display_make_number can currently set v->error when converting to a double.
749 * This is to provide the same look&feel as V3 even though gcalctool
750 * now does internal arithmetic to "infinite" precision.
752 * XXX: Needs to be improved. Shouldn't need to convert to a double in
753 * order to do these tests.
756 double number = mp_cast_to_double(MPnumber);
758 val = fabs(number);
759 if (v->error && !ignoreError) {
760 target[0] = '\0';
761 return;
763 // FIXME: Do this based on the number of digits, not actual values
764 if ((display->format == ENG) ||
765 (display->format == SCI) ||
766 (display->format == FIX && val != 0.0 && (val > max_fix[base]))) {
767 make_eng_sci(display, target, target_len, MPnumber, base);
768 } else {
769 mp_cast_to_string(MPnumber, basevals[base], v->accuracy, !v->display.show_zeroes, target, target_len);