Make the currency code OO
[gcalctool.git] / src / math-equation.c
blob082a3c18a1c57182c31d6a5e7d55e7a894d217d5
1 /* Copyright (c) 1987-2008 Sun Microsystems, Inc. All Rights Reserved.
2 * Copyright (c) 2008-2009 Robert Ancell
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2, or (at your option)
7 * any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
17 * 02111-1307, USA.
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <ctype.h>
24 #include <math.h>
25 #include <errno.h>
26 #include <glib.h>
27 #include <glib/gi18n.h>
29 #include "math-equation.h"
31 #include "mp.h"
32 #include "mp-equation.h"
33 #include "mp-serializer.h"
34 #include "math-enums.h"
37 enum {
38 PROP_0,
39 PROP_STATUS,
40 PROP_DISPLAY,
41 PROP_EQUATION,
42 PROP_NUMBER_MODE,
43 PROP_ACCURACY,
44 PROP_SHOW_THOUSANDS_SEPARATORS,
45 PROP_SHOW_TRAILING_ZEROES,
46 PROP_NUMBER_FORMAT,
47 PROP_BASE,
48 PROP_WORD_SIZE,
49 PROP_ANGLE_UNITS,
50 PROP_SOURCE_CURRENCY,
51 PROP_TARGET_CURRENCY,
52 PROP_SOURCE_UNITS,
53 PROP_TARGET_UNITS,
54 PROP_SERIALIZER
57 static GType number_mode_type, number_format_type, angle_unit_type;
59 #define MAX_DIGITS 512
61 /* Expression mode state */
62 typedef struct {
63 MPNumber ans; /* Previously calculated answer */
64 gchar *expression; /* Expression entered by user */
65 gint ans_start, ans_end; /* Start and end characters for ans variable in expression */
66 gint cursor; /* ??? */
67 NumberMode number_mode; /* ??? */
68 gboolean can_super_minus; /* TRUE if entering minus can generate a superscript minus */
69 gboolean entered_multiply; /* Last insert was a multiply character */
70 gchar *status; /* Equation status */
71 } MathEquationState;
73 struct MathEquationPrivate
75 GtkTextTag *ans_tag;
77 gint word_size; /* Word size in bits */
78 MPAngleUnit angle_units; /* Units for trigonometric functions */
79 char *source_currency;
80 char *target_currency;
81 char *source_units;
82 char *target_units;
83 NumberMode number_mode; /* ??? */
84 gboolean can_super_minus; /* TRUE if entering minus can generate a superscript minus */
86 gunichar digits[16]; /* Localized digits */
88 GtkTextMark *ans_start, *ans_end;
90 MathEquationState state; /* Equation state */
91 GList *undo_stack; /* History of expression mode states */
92 GList *redo_stack;
93 gboolean in_undo_operation;
95 gboolean in_reformat;
97 gboolean in_delete;
99 gboolean in_solve;
101 MathVariables *variables;
102 UnitManager *unit_manager;
103 MpSerializer *serializer;
105 GAsyncQueue *queue;
108 typedef struct {
109 MPNumber *number_result;
110 gchar *text_result;
111 gchar *error;
112 } SolveData;
114 G_DEFINE_TYPE (MathEquation, math_equation, GTK_TYPE_TEXT_BUFFER);
117 MathEquation *
118 math_equation_new()
120 return g_object_new(math_equation_get_type(), NULL);
124 MathVariables *
125 math_equation_get_variables(MathEquation *equation)
127 return equation->priv->variables;
131 UnitManager *
132 math_equation_get_unit_manager(MathEquation *equation)
134 return equation->priv->unit_manager;
138 static void
139 get_ans_offsets(MathEquation *equation, gint *start, gint *end)
141 GtkTextIter iter;
143 if (!equation->priv->ans_start) {
144 *start = *end = -1;
145 return;
148 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, equation->priv->ans_start);
149 *start = gtk_text_iter_get_offset(&iter);
150 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, equation->priv->ans_end);
151 *end = gtk_text_iter_get_offset(&iter);
155 static void
156 reformat_ans(MathEquation *equation)
158 if (!equation->priv->ans_start)
159 return;
161 gchar *orig_ans_text;
162 gchar *ans_text;
163 GtkTextIter ans_start, ans_end;
165 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &ans_start, equation->priv->ans_start);
166 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &ans_end, equation->priv->ans_end);
167 orig_ans_text = gtk_text_buffer_get_text(GTK_TEXT_BUFFER(equation), &ans_start, &ans_end, FALSE);
168 ans_text = mp_serializer_to_string(equation->priv->serializer, &equation->priv->state.ans);
169 if (strcmp(orig_ans_text, ans_text) != 0) {
170 gint start;
172 equation->priv->in_undo_operation = TRUE;
173 equation->priv->in_reformat = TRUE;
175 start = gtk_text_iter_get_offset(&ans_start);
176 gtk_text_buffer_delete(GTK_TEXT_BUFFER(equation), &ans_start, &ans_end);
177 gtk_text_buffer_insert_with_tags(GTK_TEXT_BUFFER(equation), &ans_end, ans_text, -1, equation->priv->ans_tag, NULL);
179 /* There seems to be a bug in the marks as they alternate being the correct and incorrect ways. Reset them */
180 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &ans_start, start);
181 gtk_text_buffer_move_mark(GTK_TEXT_BUFFER(equation), equation->priv->ans_start, &ans_start);
182 gtk_text_buffer_move_mark(GTK_TEXT_BUFFER(equation), equation->priv->ans_end, &ans_end);
184 equation->priv->in_reformat = FALSE;
185 equation->priv->in_undo_operation = FALSE;
187 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &ans_start, equation->priv->ans_start);
188 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &ans_end, equation->priv->ans_end);
189 g_free(orig_ans_text);
190 g_free(ans_text);
194 static gint
195 count_digits(MathEquation *equation, const gchar *text)
197 const gchar *read_iter;
198 gint count = 0;
200 read_iter = text;
201 while (*read_iter != '\0') {
202 if (!g_unichar_isdigit(g_utf8_get_char(read_iter)))
203 return count;
205 read_iter = g_utf8_next_char(read_iter);
207 /* Allow a thousands separator between digits follow a digit */
208 if (g_utf8_get_char(read_iter) == mp_serializer_get_thousands_separator(equation->priv->serializer)) {
209 read_iter = g_utf8_next_char(read_iter);
210 if (!g_unichar_isdigit(g_utf8_get_char(read_iter)))
211 return count;
214 count++;
217 return count;
221 static void
222 reformat_separators(MathEquation *equation)
224 gchar *text, *read_iter;
225 gint ans_start, ans_end;
226 gint offset, digit_offset = 0;
227 gboolean in_number = FALSE, in_radix = FALSE, last_is_tsep = FALSE;
229 equation->priv->in_undo_operation = TRUE;
230 equation->priv->in_reformat = TRUE;
232 text = math_equation_get_display(equation);
233 get_ans_offsets(equation, &ans_start, &ans_end);
234 for (read_iter = text, offset = 0; *read_iter != '\0'; read_iter = g_utf8_next_char(read_iter), offset++) {
235 gunichar c;
236 gboolean expect_tsep;
238 /* See what digit this character is */
239 c = g_utf8_get_char(read_iter);
241 expect_tsep = mp_serializer_get_show_thousands_separators(equation->priv->serializer) &&
242 in_number && !in_radix && !last_is_tsep &&
243 digit_offset > 0 && digit_offset % mp_serializer_get_thousands_separator_count(equation->priv->serializer) == 0;
244 last_is_tsep = FALSE;
246 /* Don't mess with ans */
247 if (offset >= ans_start && offset <= ans_end) {
248 in_number = in_radix = FALSE;
249 continue;
251 if (g_unichar_isdigit(c)) {
252 if (!in_number)
253 digit_offset = count_digits(equation, read_iter);
254 in_number = TRUE;
256 /* Expected a thousands separator between these digits - insert it */
257 if (expect_tsep) {
258 GtkTextIter iter;
259 gchar buffer[7];
260 gint len;
262 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &iter, offset);
263 len = g_unichar_to_utf8(mp_serializer_get_thousands_separator(equation->priv->serializer), buffer);
264 buffer[len] = '\0';
265 gtk_text_buffer_insert(GTK_TEXT_BUFFER(equation), &iter, buffer, -1);
266 offset++;
267 last_is_tsep = TRUE;
270 digit_offset--;
272 else if (c == mp_serializer_get_radix(equation->priv->serializer)) {
273 in_number = in_radix = TRUE;
275 else if (c == mp_serializer_get_thousands_separator(equation->priv->serializer)) {
276 /* Didn't expect thousands separator - delete it */
277 if (!expect_tsep && in_number) {
278 GtkTextIter start, end;
279 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &start, offset);
280 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &end, offset + 1);
281 gtk_text_buffer_delete(GTK_TEXT_BUFFER(equation), &start, &end);
282 offset--;
284 else
285 last_is_tsep = TRUE;
287 else {
288 in_number = in_radix = FALSE;
292 g_free(text);
294 equation->priv->in_reformat = FALSE;
295 equation->priv->in_undo_operation = FALSE;
299 static void
300 reformat_display(MathEquation *equation, gint old_base)
302 /* Change ans */
303 reformat_ans(equation);
305 /* Add/remove thousands separators */
306 reformat_separators(equation);
310 static MathEquationState *
311 get_current_state(MathEquation *equation)
313 MathEquationState *state;
314 gint ans_start = -1, ans_end = -1;
316 state = g_malloc0(sizeof(MathEquationState));
318 if (equation->priv->ans_start)
320 GtkTextIter iter;
321 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, equation->priv->ans_start);
322 ans_start = gtk_text_iter_get_offset(&iter);
323 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, equation->priv->ans_end);
324 ans_end = gtk_text_iter_get_offset(&iter);
327 mp_set_from_mp(&equation->priv->state.ans, &state->ans);
328 state->expression = math_equation_get_display(equation);
329 state->ans_start = ans_start;
330 state->ans_end = ans_end;
331 g_object_get(G_OBJECT(equation), "cursor-position", &state->cursor, NULL);
332 state->number_mode = equation->priv->number_mode;
333 state->can_super_minus = equation->priv->can_super_minus;
334 state->entered_multiply = equation->priv->state.entered_multiply;
335 state->status = g_strdup(equation->priv->state.status);
337 return state;
341 static void
342 free_state(MathEquationState *state)
344 g_free(state->expression);
345 g_free(state->status);
346 g_free(state);
350 static void
351 math_equation_push_undo_stack(MathEquation *equation)
353 GList *link;
354 MathEquationState *state;
356 if (equation->priv->in_undo_operation)
357 return;
359 math_equation_set_status(equation, "");
361 /* Can't redo anymore */
362 for (link = equation->priv->redo_stack; link; link = link->next) {
363 state = link->data;
364 free_state(state);
366 g_list_free(equation->priv->redo_stack);
367 equation->priv->redo_stack = NULL;
369 state = get_current_state(equation);
370 equation->priv->undo_stack = g_list_prepend(equation->priv->undo_stack, state);
374 static void
375 clear_ans(MathEquation *equation, gboolean remove_tag)
377 if (!equation->priv->ans_start)
378 return;
380 if (remove_tag) {
381 GtkTextIter start, end;
383 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &start, equation->priv->ans_start);
384 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &end, equation->priv->ans_end);
385 gtk_text_buffer_remove_tag(GTK_TEXT_BUFFER(equation), equation->priv->ans_tag, &start, &end);
388 gtk_text_buffer_delete_mark(GTK_TEXT_BUFFER(equation), equation->priv->ans_start);
389 gtk_text_buffer_delete_mark(GTK_TEXT_BUFFER(equation), equation->priv->ans_end);
390 equation->priv->ans_start = NULL;
391 equation->priv->ans_end = NULL;
395 static void
396 apply_state(MathEquation *equation, MathEquationState *state)
398 GtkTextIter cursor;
400 /* Disable undo detection */
401 equation->priv->in_undo_operation = TRUE;
403 mp_set_from_mp(&state->ans, &equation->priv->state.ans);
405 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), state->expression, -1);
406 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &cursor, state->cursor);
407 gtk_text_buffer_place_cursor(GTK_TEXT_BUFFER(equation), &cursor);
408 clear_ans(equation, FALSE);
409 if (state->ans_start >= 0) {
410 GtkTextIter start, end;
412 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &start, state->ans_start);
413 equation->priv->ans_start = gtk_text_buffer_create_mark(GTK_TEXT_BUFFER(equation), NULL, &start, FALSE);
414 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &end, state->ans_end);
415 equation->priv->ans_end = gtk_text_buffer_create_mark(GTK_TEXT_BUFFER(equation), NULL, &end, TRUE);
416 gtk_text_buffer_apply_tag(GTK_TEXT_BUFFER(equation), equation->priv->ans_tag, &start, &end);
419 math_equation_set_number_mode(equation, state->number_mode);
420 equation->priv->can_super_minus = state->can_super_minus;
421 equation->priv->state.entered_multiply = state->entered_multiply;
422 math_equation_set_status(equation, state->status);
424 equation->priv->in_undo_operation = FALSE;
428 void
429 math_equation_copy(MathEquation *equation)
431 GtkTextIter start, end;
432 gchar *text;
434 if (!gtk_text_buffer_get_selection_bounds(GTK_TEXT_BUFFER(equation), &start, &end))
435 gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(equation), &start, &end);
437 text = gtk_text_buffer_get_text(GTK_TEXT_BUFFER(equation), &start, &end, FALSE);
438 gtk_clipboard_set_text(gtk_clipboard_get(GDK_NONE), text, -1);
439 g_free(text);
443 static void
444 on_paste(GtkClipboard *clipboard, const gchar *text, gpointer data)
446 MathEquation *equation = data;
447 if (text != NULL)
448 math_equation_insert(equation, text);
452 void
453 math_equation_paste(MathEquation *equation)
455 gtk_clipboard_request_text(gtk_clipboard_get(GDK_NONE), on_paste, equation);
459 void
460 math_equation_undo(MathEquation *equation)
462 GList *link;
463 MathEquationState *state;
465 if (!equation->priv->undo_stack) {
466 math_equation_set_status(equation,
467 /* Error shown when trying to undo with no undo history */
468 _("No undo history"));
469 return;
472 link = equation->priv->undo_stack;
473 equation->priv->undo_stack = g_list_remove_link(equation->priv->undo_stack, link);
474 state = link->data;
475 g_list_free(link);
477 equation->priv->redo_stack = g_list_prepend(equation->priv->redo_stack, get_current_state(equation));
479 apply_state(equation, state);
480 free_state(state);
484 void
485 math_equation_redo(MathEquation *equation)
487 GList *link;
488 MathEquationState *state;
490 if (!equation->priv->redo_stack) {
491 math_equation_set_status(equation,
492 /* Error shown when trying to redo with no redo history */
493 _("No redo history"));
494 return;
497 link = equation->priv->redo_stack;
498 equation->priv->redo_stack = g_list_remove_link(equation->priv->redo_stack, link);
499 state = link->data;
500 g_list_free(link);
502 equation->priv->undo_stack = g_list_prepend(equation->priv->undo_stack, get_current_state(equation));
504 apply_state(equation, state);
505 free_state(state);
509 gunichar
510 math_equation_get_digit_text(MathEquation *equation, guint digit)
512 return equation->priv->digits[digit];
516 void
517 math_equation_set_accuracy(MathEquation *equation, gint accuracy)
519 gint old_accuracy = mp_serializer_get_accuracy(equation->priv->serializer);
520 if (old_accuracy == accuracy)
521 return;
522 mp_serializer_set_accuracy(equation->priv->serializer, accuracy);
523 reformat_display(equation, mp_serializer_get_base(equation->priv->serializer));
524 g_object_notify(G_OBJECT(equation), "accuracy");
528 gint
529 math_equation_get_accuracy(MathEquation *equation)
531 return mp_serializer_get_accuracy(equation->priv->serializer);
535 void
536 math_equation_set_show_thousands_separators(MathEquation *equation, gboolean visible)
538 gboolean old_visible = mp_serializer_get_show_thousands_separators(equation->priv->serializer);
539 if (old_visible == visible)
540 return;
541 mp_serializer_set_show_thousands_separators(equation->priv->serializer, visible);
542 reformat_display(equation, mp_serializer_get_base(equation->priv->serializer));
543 g_object_notify(G_OBJECT(equation), "show-thousands-separators");
547 gboolean
548 math_equation_get_show_thousands_separators(MathEquation *equation)
550 return mp_serializer_get_show_thousands_separators(equation->priv->serializer);
554 void
555 math_equation_set_show_trailing_zeroes(MathEquation *equation, gboolean visible)
557 gboolean old_visible = mp_serializer_get_show_trailing_zeroes(equation->priv->serializer);
558 if (old_visible == visible)
559 return;
560 mp_serializer_set_show_trailing_zeroes(equation->priv->serializer, visible);
561 reformat_display(equation, mp_serializer_get_base(equation->priv->serializer));
562 g_object_notify(G_OBJECT(equation), "show-trailing-zeroes");
566 gboolean
567 math_equation_get_show_trailing_zeroes(MathEquation *equation)
569 return mp_serializer_get_show_trailing_zeroes(equation->priv->serializer);
573 void
574 math_equation_set_number_format(MathEquation *equation, MpDisplayFormat format)
576 MpDisplayFormat old_format = mp_serializer_get_number_format(equation->priv->serializer);
577 if (old_format == format)
578 return;
580 mp_serializer_set_number_format(equation->priv->serializer, format);
581 reformat_display(equation, mp_serializer_get_base(equation->priv->serializer));
582 g_object_notify(G_OBJECT(equation), "number-format");
586 MpDisplayFormat
587 math_equation_get_number_format(MathEquation *equation)
589 return mp_serializer_get_number_format(equation->priv->serializer);
593 void
594 math_equation_set_base(MathEquation *equation, gint base)
596 gint old_base = mp_serializer_get_base(equation->priv->serializer);
598 if (old_base == base)
599 return;
601 mp_serializer_set_base(equation->priv->serializer, base);
602 reformat_display(equation, old_base);
603 g_object_notify(G_OBJECT(equation), "base");
607 gint
608 math_equation_get_base(MathEquation *equation)
610 return mp_serializer_get_base(equation->priv->serializer);
614 void
615 math_equation_set_word_size(MathEquation *equation, gint word_size)
617 if (equation->priv->word_size == word_size)
618 return;
619 equation->priv->word_size = word_size;
620 g_object_notify(G_OBJECT(equation), "word-size");
624 gint
625 math_equation_get_word_size(MathEquation *equation)
627 return equation->priv->word_size;
631 void
632 math_equation_set_angle_units(MathEquation *equation, MPAngleUnit angle_units)
634 if (equation->priv->angle_units == angle_units)
635 return;
636 equation->priv->angle_units = angle_units;
637 g_object_notify(G_OBJECT(equation), "angle-units");
641 MPAngleUnit
642 math_equation_get_angle_units(MathEquation *equation)
644 return equation->priv->angle_units;
648 void
649 math_equation_set_source_currency(MathEquation *equation, const gchar *currency)
651 if (strcmp(equation->priv->source_currency, currency) == 0)
652 return;
653 g_free(equation->priv->source_currency);
654 equation->priv->source_currency = g_strdup(currency);
655 g_object_notify(G_OBJECT(equation), "source-currency");
659 const gchar *
660 math_equation_get_source_currency(MathEquation *equation)
662 return equation->priv->source_currency;
666 void
667 math_equation_set_target_currency(MathEquation *equation, const gchar *currency)
669 if (strcmp(equation->priv->target_currency, currency) == 0)
670 return;
671 g_free(equation->priv->target_currency);
672 equation->priv->target_currency = g_strdup(currency);
673 g_object_notify(G_OBJECT(equation), "target-currency");
677 const gchar *
678 math_equation_get_target_currency(MathEquation *equation)
680 return equation->priv->target_currency;
684 void
685 math_equation_set_source_units(MathEquation *equation, const gchar *units)
687 if (strcmp(equation->priv->source_units, units) == 0)
688 return;
689 g_free(equation->priv->source_units);
690 equation->priv->source_units = g_strdup(units);
691 g_object_notify(G_OBJECT(equation), "source-units");
694 const gchar *
695 math_equation_get_source_units(MathEquation *equation)
697 return equation->priv->source_units;
701 void
702 math_equation_set_target_units(MathEquation *equation, const gchar *units)
704 if (strcmp(equation->priv->target_units, units) == 0)
705 return;
706 g_free(equation->priv->target_units);
707 equation->priv->target_units = g_strdup(units);
708 g_object_notify(G_OBJECT(equation), "target-units");
712 const gchar *
713 math_equation_get_target_units(MathEquation *equation)
715 return equation->priv->target_units;
719 void
720 math_equation_set_status(MathEquation *equation, const gchar *status)
722 if (strcmp(equation->priv->state.status, status) == 0)
723 return;
725 g_free(equation->priv->state.status);
726 equation->priv->state.status = g_strdup(status);
727 g_object_notify(G_OBJECT(equation), "status");
731 const gchar *
732 math_equation_get_status(MathEquation *equation)
734 return equation->priv->state.status;
738 gboolean
739 math_equation_is_empty(MathEquation *equation)
741 return gtk_text_buffer_get_char_count(GTK_TEXT_BUFFER(equation)) == 0;
745 gboolean
746 math_equation_is_result(MathEquation *equation)
748 char *text;
749 gboolean result;
751 text = math_equation_get_equation(equation);
752 result = strcmp(text, "ans") == 0;
753 g_free(text);
755 return result;
759 gchar *
760 math_equation_get_display(MathEquation *equation)
762 GtkTextIter start, end;
764 gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(equation), &start, &end);
765 return gtk_text_buffer_get_text(GTK_TEXT_BUFFER(equation), &start, &end, FALSE);
769 gchar *
770 math_equation_get_equation(MathEquation *equation)
772 gchar *text;
773 GString *eq_text;
774 gint ans_start = -1, ans_end = -1, offset;
775 const gchar *read_iter;
776 gboolean last_is_digit = FALSE;
778 text = math_equation_get_display(equation);
779 eq_text = g_string_sized_new(strlen(text));
781 if (equation->priv->ans_start)
782 get_ans_offsets(equation, &ans_start, &ans_end);
784 for (read_iter = text, offset = 0; *read_iter != '\0'; read_iter = g_utf8_next_char(read_iter), offset++) {
785 gunichar c;
786 gboolean is_digit, next_is_digit;
788 c = g_utf8_get_char(read_iter);
789 is_digit = g_unichar_isdigit(c);
790 next_is_digit = g_unichar_isdigit(g_utf8_get_char(g_utf8_next_char(read_iter)));
792 /* Replace ans text with variable */
793 if (offset == ans_start) {
794 g_string_append(eq_text, "ans");
795 read_iter = g_utf8_offset_to_pointer(read_iter, ans_end - ans_start - 1);
796 offset += ans_end - ans_start - 1;
797 is_digit = FALSE;
798 continue;
801 /* Ignore thousands separators */
802 if (c == mp_serializer_get_thousands_separator(equation->priv->serializer) && last_is_digit && next_is_digit)
804 /* Substitute radix character */
805 else if (c == mp_serializer_get_radix(equation->priv->serializer) && (last_is_digit || next_is_digit))
806 g_string_append_unichar(eq_text, '.');
807 else
808 g_string_append_unichar(eq_text, c);
810 last_is_digit = is_digit;
812 g_free(text);
814 text = eq_text->str;
815 g_string_free(eq_text, FALSE);
817 return text;
821 gboolean
822 math_equation_get_number(MathEquation *equation, MPNumber *z)
824 gchar *text;
825 gboolean result;
827 text = math_equation_get_equation(equation);
828 result = !mp_serializer_from_string(equation->priv->serializer, text, z);
829 g_free(text);
831 return result;
835 MpSerializer *
836 math_equation_get_serializer(MathEquation *equation)
838 return equation->priv->serializer;
842 void
843 math_equation_set_number_mode(MathEquation *equation, NumberMode mode)
845 if (equation->priv->number_mode == mode)
846 return;
848 equation->priv->can_super_minus = mode == SUPERSCRIPT;
850 equation->priv->number_mode = mode;
851 g_object_notify(G_OBJECT(equation), "number-mode");
855 NumberMode
856 math_equation_get_number_mode(MathEquation *equation)
858 return equation->priv->number_mode;
862 gboolean
863 math_equation_in_solve(MathEquation *equation)
865 return equation->priv->in_solve;
869 const MPNumber *
870 math_equation_get_answer(MathEquation *equation)
872 return &equation->priv->state.ans;
876 void
877 math_equation_store(MathEquation *equation, const gchar *name)
879 MPNumber t;
881 if (!math_equation_get_number(equation, &t))
882 math_equation_set_status(equation, _("No sane value to store"));
883 else
884 math_variables_set(equation->priv->variables, name, &t);
888 void
889 math_equation_recall(MathEquation *equation, const gchar *name)
891 math_equation_insert(equation, name);
895 void
896 math_equation_set(MathEquation *equation, const gchar *text)
899 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), text, -1);
900 clear_ans(equation, FALSE);
904 void
905 math_equation_set_number(MathEquation *equation, const MPNumber *x)
907 char *text;
908 GtkTextIter start, end;
910 /* Show the number in the user chosen format */
911 text = mp_serializer_to_string(equation->priv->serializer, x);
912 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), text, -1);
913 mp_set_from_mp(x, &equation->priv->state.ans);
915 /* Mark this text as the answer variable */
916 gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(equation), &start, &end);
917 clear_ans(equation, FALSE);
918 equation->priv->ans_start = gtk_text_buffer_create_mark(GTK_TEXT_BUFFER(equation), NULL, &start, FALSE);
919 equation->priv->ans_end = gtk_text_buffer_create_mark(GTK_TEXT_BUFFER(equation), NULL, &end, TRUE);
920 gtk_text_buffer_apply_tag(GTK_TEXT_BUFFER(equation), equation->priv->ans_tag, &start, &end);
921 g_free(text);
925 void
926 math_equation_insert(MathEquation *equation, const gchar *text)
928 /* Replace ** with ^ (not on all keyboards) */
929 if (!gtk_text_buffer_get_has_selection(GTK_TEXT_BUFFER(equation)) &&
930 strcmp(text, "×") == 0 && equation->priv->state.entered_multiply) {
931 GtkTextIter iter;
933 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, gtk_text_buffer_get_insert(GTK_TEXT_BUFFER(equation)));
934 gtk_text_buffer_backspace(GTK_TEXT_BUFFER(equation), &iter, TRUE, TRUE);
935 gtk_text_buffer_insert_at_cursor(GTK_TEXT_BUFFER(equation), "^", -1);
936 return;
939 /* Start new equation when entering digits after existing result */
940 if(math_equation_is_result(equation) && g_unichar_isdigit(g_utf8_get_char(text)))
941 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), "", -1);
943 /* Can't enter superscript minus after entering digits */
944 if (strstr("⁰¹²³⁴⁵⁶⁷⁸⁹", text) != NULL || strcmp("⁻", text) == 0)
945 equation->priv->can_super_minus = FALSE;
947 /* Disable super/subscript mode when finished entering */
948 if (strstr("⁻⁰¹²³⁴⁵⁶⁷⁸⁹₀₁₂₃₄₅₆₇₈₉", text) == NULL)
949 math_equation_set_number_mode(equation, NORMAL);
951 gtk_text_buffer_delete_selection(GTK_TEXT_BUFFER(equation), FALSE, FALSE);
952 gtk_text_buffer_insert_at_cursor(GTK_TEXT_BUFFER(equation), text, -1);
956 void
957 math_equation_insert_digit(MathEquation *equation, guint digit)
959 static const char *subscript_digits[] = {"₀", "₁", "₂", "₃", "₄", "₅", "₆", "₇", "₈", "₉", NULL};
960 static const char *superscript_digits[] = {"⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹", NULL};
962 if (equation->priv->number_mode == NORMAL || digit >= 10) {
963 gchar buffer[7];
964 gint len;
965 len = g_unichar_to_utf8(math_equation_get_digit_text(equation, digit), buffer);
966 buffer[len] = '\0';
967 math_equation_insert(equation, buffer);
969 else if (equation->priv->number_mode == SUPERSCRIPT)
970 math_equation_insert(equation, superscript_digits[digit]);
971 else if (equation->priv->number_mode == SUBSCRIPT)
972 math_equation_insert(equation, subscript_digits[digit]);
976 void
977 math_equation_insert_numeric_point(MathEquation *equation)
979 gchar buffer[7];
980 gint len;
981 len = g_unichar_to_utf8(mp_serializer_get_radix(equation->priv->serializer), buffer);
982 buffer[len] = '\0';
983 math_equation_insert(equation, buffer);
987 void
988 math_equation_insert_number(MathEquation *equation, const MPNumber *x)
990 char *text;
991 text = mp_serializer_to_string(equation->priv->serializer, x);
992 math_equation_insert(equation, text);
993 g_free(text);
997 void
998 math_equation_insert_exponent(MathEquation *equation)
1000 math_equation_insert(equation, "×10");
1001 math_equation_set_number_mode(equation, SUPERSCRIPT);
1005 void
1006 math_equation_insert_subtract(MathEquation *equation)
1008 if (equation->priv->number_mode == SUPERSCRIPT && equation->priv->can_super_minus) {
1009 math_equation_insert(equation, "⁻");
1010 equation->priv->can_super_minus = FALSE;
1012 else {
1013 math_equation_insert(equation, "−");
1014 math_equation_set_number_mode(equation, NORMAL);
1019 static int
1020 variable_is_defined(const char *name, void *data)
1022 MathEquation *equation = data;
1023 char *c, *lower_name;
1025 lower_name = strdup(name);
1026 for (c = lower_name; *c; c++)
1027 *c = tolower(*c);
1029 if (strcmp(lower_name, "rand") == 0 ||
1030 strcmp(lower_name, "ans") == 0) {
1031 g_free(lower_name);
1032 return 1;
1034 g_free(lower_name);
1036 return math_variables_get(equation->priv->variables, name) != NULL;
1040 static int
1041 get_variable(const char *name, MPNumber *z, void *data)
1043 char *c, *lower_name;
1044 int result = 1;
1045 MathEquation *equation = data;
1046 MPNumber *t;
1048 lower_name = strdup(name);
1049 for (c = lower_name; *c; c++)
1050 *c = tolower(*c);
1052 if (strcmp(lower_name, "rand") == 0)
1053 mp_set_from_random(z);
1054 else if (strcmp(lower_name, "ans") == 0)
1055 mp_set_from_mp(&equation->priv->state.ans, z);
1056 else {
1057 t = math_variables_get(equation->priv->variables, name);
1058 if (t)
1059 mp_set_from_mp(t, z);
1060 else
1061 result = 0;
1064 free(lower_name);
1066 return result;
1070 static void
1071 set_variable(const char *name, const MPNumber *x, void *data)
1073 MathEquation *equation = data;
1074 /* FIXME: Don't allow writing to built-in variables, e.g. ans, rand, sin, ... */
1075 math_variables_set(equation->priv->variables, name, x);
1079 static int
1080 convert(const MPNumber *x, const char *x_units, const char *z_units, MPNumber *z, void *data)
1082 MathEquation *equation = data;
1083 return unit_manager_convert(equation->priv->unit_manager, x, x_units, z_units, z);
1087 static int
1088 parse(MathEquation *equation, const char *text, MPNumber *z, char **error_token)
1090 MPEquationOptions options;
1092 memset(&options, 0, sizeof(options));
1093 options.base = mp_serializer_get_base(equation->priv->serializer);
1094 options.wordlen = equation->priv->word_size;
1095 options.angle_units = equation->priv->angle_units;
1096 options.variable_is_defined = variable_is_defined;
1097 options.get_variable = get_variable;
1098 options.set_variable = set_variable;
1099 options.convert = convert;
1100 options.callback_data = equation;
1102 return mp_equation_parse(text, &options, z, error_token);
1107 * Executed in separate thread. It is thus not a good idea to write to anything
1108 * in MathEquation but the async queue from here.
1110 static gpointer
1111 math_equation_solve_real(gpointer data)
1113 MathEquation *equation = MATH_EQUATION(data);
1114 SolveData *solvedata = g_slice_new0(SolveData);
1116 gint n_brackets = 0, result;
1117 gchar *c, *text, *error_token;
1118 GString *equation_text;
1119 MPNumber z;
1121 text = math_equation_get_equation(equation);
1122 equation_text = g_string_new(text);
1123 g_free(text);
1124 /* Count the number of brackets and automatically add missing closing brackets */
1125 for (c = equation_text->str; *c; c++) {
1126 if (*c == '(')
1127 n_brackets++;
1128 else if (*c == ')')
1129 n_brackets--;
1131 while (n_brackets > 0) {
1132 g_string_append_c(equation_text, ')');
1133 n_brackets--;
1137 result = parse(equation, equation_text->str, &z, &error_token);
1138 g_string_free(equation_text, TRUE);
1140 switch (result) {
1141 case PARSER_ERR_NONE:
1142 solvedata->number_result = g_slice_new(MPNumber);
1143 mp_set_from_mp(&z, solvedata->number_result);
1144 break;
1146 case PARSER_ERR_OVERFLOW:
1147 solvedata->error = g_strdup(/* Error displayed to user when they perform a bitwise operation on numbers greater than the current word */
1148 _("Overflow. Try a bigger word size"));
1149 break;
1151 case PARSER_ERR_UNKNOWN_VARIABLE:
1152 solvedata->error = g_strdup_printf(/* Error displayed to user when they an unknown variable is entered */
1153 _("Unknown variable '%s'"), error_token);
1154 break;
1156 case PARSER_ERR_UNKNOWN_FUNCTION:
1157 solvedata->error = g_strdup_printf(/* Error displayed to user when an unknown function is entered */
1158 _("Function '%s' is not defined"), error_token);
1159 break;
1161 case PARSER_ERR_UNKNOWN_CONVERSION:
1162 solvedata->error = g_strdup(/* Error displayed to user when an conversion with unknown units is attempted */
1163 _("Unknown conversion"));
1164 break;
1166 case PARSER_ERR_MP:
1167 solvedata->error = g_strdup(mp_get_error());
1168 break;
1170 default:
1171 solvedata->error = g_strdup(/* Error displayed to user when they enter an invalid calculation */
1172 _("Malformed expression"));
1173 break;
1175 g_async_queue_push(equation->priv->queue, solvedata);
1177 return NULL;
1181 static gboolean
1182 math_equation_show_in_progress(gpointer data)
1184 MathEquation *equation = MATH_EQUATION(data);
1185 if (equation->priv->in_solve)
1186 math_equation_set_status(equation, "Calculating");
1187 return false;
1191 static gboolean
1192 math_equation_look_for_answer(gpointer data)
1194 MathEquation *equation = MATH_EQUATION(data);
1195 SolveData *result = g_async_queue_try_pop(equation->priv->queue);
1197 if (result == NULL)
1198 return true;
1200 equation->priv->in_solve = false;
1202 if (!result->error)
1203 math_equation_set_status(equation, "");
1205 if (result->error != NULL) {
1206 math_equation_set_status(equation, result->error);
1207 g_free(result->error);
1209 else if (result->number_result != NULL) {
1210 math_equation_set_number(equation, result->number_result);
1211 g_slice_free(MPNumber, result->number_result);
1213 else if (result->text_result != NULL) {
1214 math_equation_set(equation, result->text_result);
1215 g_free(result->text_result);
1217 g_slice_free(SolveData, result);
1219 return false;
1223 void
1224 math_equation_solve(MathEquation *equation)
1226 GError *error = NULL;
1228 // FIXME: should replace calculation or give error message
1229 if (equation->priv->in_solve)
1230 return;
1232 if (math_equation_is_empty(equation))
1233 return;
1235 /* If showing a result return to the equation that caused it */
1236 // FIXME: Result may not be here due to solve (i.e. the user may have entered "ans")
1237 if (math_equation_is_result(equation)) {
1238 math_equation_undo(equation);
1239 return;
1242 equation->priv->in_solve = true;
1244 math_equation_set_number_mode(equation, NORMAL);
1246 g_thread_create(math_equation_solve_real, equation, false, &error);
1248 if (error)
1249 g_warning("Error spawning thread for calculations: %s\n", error->message);
1251 g_timeout_add(50, math_equation_look_for_answer, equation);
1252 g_timeout_add(100, math_equation_show_in_progress, equation);
1256 static gpointer
1257 math_equation_factorize_real(gpointer data)
1259 GString *text;
1260 GList *factors, *factor;
1261 MPNumber x;
1262 MathEquation *equation = MATH_EQUATION(data);
1263 SolveData *result = g_slice_new0(SolveData);
1265 math_equation_get_number(equation, &x);
1266 factors = mp_factorize(&x);
1268 text = g_string_new("");
1270 for (factor = factors; factor; factor = factor->next) {
1271 gchar *temp;
1272 MPNumber *n;
1274 n = factor->data;
1275 temp = mp_serializer_to_string(equation->priv->serializer, n);
1276 g_string_append(text, temp);
1277 if (factor->next)
1278 g_string_append(text, "×");
1279 g_slice_free(MPNumber, n);
1280 g_free(temp);
1282 g_list_free(factors);
1284 result->text_result = g_strndup(text->str, text->len);
1285 g_async_queue_push(equation->priv->queue, result);
1286 g_string_free(text, TRUE);
1288 return NULL;
1292 void
1293 math_equation_factorize(MathEquation *equation)
1295 MPNumber x;
1296 GError *error = NULL;
1298 // FIXME: should replace calculation or give error message
1299 if (equation->priv->in_solve)
1300 return;
1302 if (!math_equation_get_number(equation, &x) || !mp_is_integer(&x)) {
1303 /* Error displayed when trying to factorize a non-integer value */
1304 math_equation_set_status(equation, _("Need an integer to factorize"));
1305 return;
1308 equation->priv->in_solve = true;
1310 g_thread_create(math_equation_factorize_real, equation, false, &error);
1312 if (error)
1313 g_warning("Error spawning thread for calculations: %s\n", error->message);
1315 g_timeout_add(50, math_equation_look_for_answer, equation);
1316 g_timeout_add(100, math_equation_show_in_progress, equation);
1320 void
1321 math_equation_delete(MathEquation *equation)
1323 gint cursor;
1324 GtkTextIter start, end;
1326 g_object_get(G_OBJECT(equation), "cursor-position", &cursor, NULL);
1327 if (cursor >= gtk_text_buffer_get_char_count(GTK_TEXT_BUFFER(equation)))
1328 return;
1330 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &start, cursor);
1331 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &end, cursor+1);
1332 gtk_text_buffer_delete(GTK_TEXT_BUFFER(equation), &start, &end);
1336 void
1337 math_equation_backspace(MathEquation *equation)
1339 /* Can't delete empty display */
1340 if (math_equation_is_empty(equation))
1341 return;
1343 if (gtk_text_buffer_get_has_selection(GTK_TEXT_BUFFER(equation)))
1344 gtk_text_buffer_delete_selection(GTK_TEXT_BUFFER(equation), FALSE, FALSE);
1345 else {
1346 GtkTextIter iter;
1347 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, gtk_text_buffer_get_insert(GTK_TEXT_BUFFER(equation)));
1348 gtk_text_buffer_backspace(GTK_TEXT_BUFFER(equation), &iter, TRUE, TRUE);
1353 void
1354 math_equation_clear(MathEquation *equation)
1356 math_equation_set_number_mode(equation, NORMAL);
1357 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), "", -1);
1358 clear_ans(equation, FALSE);
1362 void
1363 math_equation_shift(MathEquation *equation, gint count)
1365 MPNumber z;
1367 if (!math_equation_get_number(equation, &z)) {
1368 math_equation_set_status(equation,
1369 /* This message is displayed in the status bar when a bit
1370 shift operation is performed and the display does not contain a number */
1371 _("No sane value to bitwise shift"));
1372 return;
1375 mp_shift(&z, count, &z);
1376 math_equation_set_number(equation, &z);
1380 void
1381 math_equation_toggle_bit(MathEquation *equation, guint bit)
1383 MPNumber x;
1384 guint64 bits;
1385 gboolean result;
1387 result = math_equation_get_number(equation, &x);
1388 if (result) {
1389 MPNumber max;
1390 mp_set_from_unsigned_integer(G_MAXUINT64, &max);
1391 if (mp_is_negative(&x) || mp_is_greater_than(&x, &max))
1392 result = FALSE;
1393 else
1394 bits = mp_cast_to_unsigned_int(&x);
1397 if (!result) {
1398 math_equation_set_status(equation,
1399 /* Message displayed when cannot toggle bit in display*/
1400 _("Displayed value not an integer"));
1401 return;
1404 bits ^= (1LL << (63 - bit));
1406 mp_set_from_unsigned_integer(bits, &x);
1408 // FIXME: Only do this if in ans format, otherwise set text in same format as previous number
1409 math_equation_set_number(equation, &x);
1413 static void
1414 math_equation_set_property(GObject *object,
1415 guint prop_id,
1416 const GValue *value,
1417 GParamSpec *pspec)
1419 MathEquation *self;
1421 self = MATH_EQUATION(object);
1423 switch (prop_id) {
1424 case PROP_STATUS:
1425 math_equation_set_status(self, g_value_get_string(value));
1426 break;
1427 case PROP_DISPLAY:
1428 math_equation_set(self, g_value_get_string(value));
1429 break;
1430 case PROP_NUMBER_MODE:
1431 math_equation_set_number_mode(self, g_value_get_int(value));
1432 break;
1433 case PROP_ACCURACY:
1434 math_equation_set_accuracy(self, g_value_get_int(value));
1435 break;
1436 case PROP_SHOW_THOUSANDS_SEPARATORS:
1437 math_equation_set_show_thousands_separators(self, g_value_get_boolean(value));
1438 break;
1439 case PROP_SHOW_TRAILING_ZEROES:
1440 math_equation_set_show_trailing_zeroes(self, g_value_get_boolean(value));
1441 break;
1442 case PROP_NUMBER_FORMAT:
1443 math_equation_set_number_format(self, g_value_get_int(value));
1444 break;
1445 case PROP_BASE:
1446 math_equation_set_base(self, g_value_get_int(value));
1447 break;
1448 case PROP_WORD_SIZE:
1449 math_equation_set_word_size(self, g_value_get_int(value));
1450 break;
1451 case PROP_ANGLE_UNITS:
1452 math_equation_set_angle_units(self, g_value_get_int(value));
1453 break;
1454 case PROP_SOURCE_CURRENCY:
1455 math_equation_set_source_currency(self, g_value_get_string(value));
1456 break;
1457 case PROP_TARGET_CURRENCY:
1458 math_equation_set_target_currency(self, g_value_get_string(value));
1459 break;
1460 case PROP_SOURCE_UNITS:
1461 math_equation_set_source_units(self, g_value_get_string(value));
1462 break;
1463 case PROP_TARGET_UNITS:
1464 math_equation_set_target_units(self, g_value_get_string(value));
1465 break;
1466 default:
1467 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
1468 break;
1473 static void
1474 math_equation_get_property(GObject *object,
1475 guint prop_id,
1476 GValue *value,
1477 GParamSpec *pspec)
1479 MathEquation *self;
1480 gchar *text;
1482 self = MATH_EQUATION(object);
1484 switch (prop_id) {
1485 case PROP_STATUS:
1486 g_value_set_string(value, self->priv->state.status);
1487 break;
1488 case PROP_DISPLAY:
1489 text = math_equation_get_display(self);
1490 g_value_set_string(value, text);
1491 g_free(text);
1492 break;
1493 case PROP_EQUATION:
1494 text = math_equation_get_equation(self);
1495 g_value_set_string(value, text);
1496 g_free(text);
1497 break;
1498 case PROP_NUMBER_MODE:
1499 g_value_set_enum(value, self->priv->number_mode);
1500 break;
1501 case PROP_ACCURACY:
1502 g_value_set_int(value, mp_serializer_get_accuracy(self->priv->serializer));
1503 break;
1504 case PROP_SHOW_THOUSANDS_SEPARATORS:
1505 g_value_set_boolean(value, mp_serializer_get_show_thousands_separators(self->priv->serializer));
1506 break;
1507 case PROP_SHOW_TRAILING_ZEROES:
1508 g_value_set_boolean(value, mp_serializer_get_show_trailing_zeroes(self->priv->serializer));
1509 break;
1510 case PROP_NUMBER_FORMAT:
1511 g_value_set_enum(value, mp_serializer_get_number_format(self->priv->serializer));
1512 break;
1513 case PROP_BASE:
1514 g_value_set_int(value, math_equation_get_base(self));
1515 break;
1516 case PROP_WORD_SIZE:
1517 g_value_set_int(value, self->priv->word_size);
1518 break;
1519 case PROP_ANGLE_UNITS:
1520 g_value_set_enum(value, self->priv->angle_units);
1521 break;
1522 case PROP_SOURCE_CURRENCY:
1523 g_value_set_string(value, self->priv->source_currency);
1524 break;
1525 case PROP_TARGET_CURRENCY:
1526 g_value_set_string(value, self->priv->target_currency);
1527 break;
1528 case PROP_SOURCE_UNITS:
1529 g_value_set_string(value, self->priv->source_units);
1530 break;
1531 case PROP_TARGET_UNITS:
1532 g_value_set_string(value, self->priv->target_units);
1533 break;
1534 case PROP_SERIALIZER:
1535 g_value_set_object(value, self->priv->serializer);
1536 break;
1537 default:
1538 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
1539 break;
1544 static void
1545 math_equation_constructed(GObject *object)
1547 GtkTextBuffer *parent_class;
1548 parent_class = g_type_class_peek_parent(MATH_EQUATION_GET_CLASS(object));
1549 if (G_OBJECT_CLASS(parent_class)->constructed)
1550 G_OBJECT_CLASS(parent_class)->constructed(object);
1552 MATH_EQUATION(object)->priv->ans_tag = gtk_text_buffer_create_tag(GTK_TEXT_BUFFER(object), NULL, "weight", PANGO_WEIGHT_BOLD, NULL);
1556 static void
1557 math_equation_class_init(MathEquationClass *klass)
1559 static GEnumValue number_mode_values[] =
1561 {NORMAL, "normal", "normal"},
1562 {SUPERSCRIPT, "superscript", "superscript"},
1563 {SUBSCRIPT, "subscript", "subscript"},
1564 {0, NULL, NULL}
1566 static GEnumValue angle_unit_values[] =
1568 {MP_RADIANS, "radians", "radians"},
1569 {MP_DEGREES, "degrees", "degrees"},
1570 {MP_GRADIANS, "gradians", "gradians"},
1571 {0, NULL, NULL}
1573 GObjectClass *object_class = G_OBJECT_CLASS(klass);
1575 object_class->get_property = math_equation_get_property;
1576 object_class->set_property = math_equation_set_property;
1577 object_class->constructed = math_equation_constructed;
1579 g_type_class_add_private(klass, sizeof(MathEquationPrivate));
1581 number_mode_type = g_enum_register_static("NumberMode", number_mode_values);
1582 number_format_type = math_mp_display_format_get_type();
1583 angle_unit_type = g_enum_register_static("AngleUnit", angle_unit_values);
1585 g_object_class_install_property(object_class,
1586 PROP_STATUS,
1587 g_param_spec_string("status",
1588 "status",
1589 "Equation status",
1591 G_PARAM_READWRITE));
1592 g_object_class_install_property(object_class,
1593 PROP_DISPLAY,
1594 g_param_spec_string("display",
1595 "display",
1596 "Displayed equation text",
1598 G_PARAM_READWRITE));
1599 g_object_class_install_property(object_class,
1600 PROP_EQUATION,
1601 g_param_spec_string("equation",
1602 "equation",
1603 "Equation text",
1605 G_PARAM_READABLE));
1606 g_object_class_install_property(object_class,
1607 PROP_NUMBER_MODE,
1608 g_param_spec_enum("number-mode",
1609 "number-mode",
1610 "Input number mode",
1611 number_mode_type,
1612 NORMAL,
1613 G_PARAM_READWRITE));
1614 g_object_class_install_property(object_class,
1615 PROP_ACCURACY,
1616 g_param_spec_int("accuracy",
1617 "accuracy",
1618 "Display accuracy",
1619 0, 20, 9,
1620 G_PARAM_READWRITE));
1621 g_object_class_install_property(object_class,
1622 PROP_SHOW_THOUSANDS_SEPARATORS,
1623 g_param_spec_boolean("show-thousands-separators",
1624 "show-thousands-separators",
1625 "Show thousands separators",
1626 TRUE,
1627 G_PARAM_READWRITE));
1628 g_object_class_install_property(object_class,
1629 PROP_SHOW_TRAILING_ZEROES,
1630 g_param_spec_boolean("show-trailing-zeroes",
1631 "show-trailing-zeroes",
1632 "Show trailing zeroes",
1633 FALSE,
1634 G_PARAM_READWRITE));
1635 g_object_class_install_property(object_class,
1636 PROP_NUMBER_FORMAT,
1637 g_param_spec_enum("number-format",
1638 "number-format",
1639 "Display format",
1640 number_format_type,
1641 MP_DISPLAY_FORMAT_FIXED,
1642 G_PARAM_READWRITE));
1643 g_object_class_install_property(object_class,
1644 PROP_BASE,
1645 g_param_spec_int("base",
1646 "base",
1647 "Default number base (derived from number-format)",
1648 2, 16, 10,
1649 G_PARAM_READWRITE));
1650 g_object_class_install_property(object_class,
1651 PROP_WORD_SIZE,
1652 g_param_spec_int("word-size",
1653 "word-size",
1654 "Word size in bits",
1655 8, 64, 64,
1656 G_PARAM_READWRITE));
1657 g_object_class_install_property(object_class,
1658 PROP_ANGLE_UNITS,
1659 g_param_spec_enum("angle-units",
1660 "angle-units",
1661 "Angle units",
1662 angle_unit_type,
1663 MP_DEGREES,
1664 G_PARAM_READWRITE));
1665 g_object_class_install_property(object_class,
1666 PROP_SOURCE_CURRENCY,
1667 g_param_spec_string("source-currency",
1668 "source-currency",
1669 "Source Currency",
1671 G_PARAM_READWRITE));
1672 g_object_class_install_property(object_class,
1673 PROP_TARGET_CURRENCY,
1674 g_param_spec_string("target-currency",
1675 "target-currency",
1676 "target Currency",
1678 G_PARAM_READWRITE));
1679 g_object_class_install_property(object_class,
1680 PROP_SOURCE_UNITS,
1681 g_param_spec_string("source-units",
1682 "source-units",
1683 "Source Units",
1685 G_PARAM_READWRITE));
1686 g_object_class_install_property(object_class,
1687 PROP_TARGET_UNITS,
1688 g_param_spec_string("target-units",
1689 "target-units",
1690 "target Units",
1692 G_PARAM_READWRITE));
1693 g_object_class_install_property(object_class,
1694 PROP_SERIALIZER,
1695 g_param_spec_object("serializer",
1696 "serializer",
1697 "Serializer",
1698 MP_TYPE_SERIALIZER,
1699 G_PARAM_READABLE));
1703 static void
1704 pre_insert_text_cb(MathEquation *equation,
1705 GtkTextIter *location,
1706 gchar *text,
1707 gint len,
1708 gpointer user_data)
1710 gunichar c;
1712 if (equation->priv->in_reformat)
1713 return;
1715 /* If following a delete then have already pushed undo stack (GtkTextBuffer
1716 doesn't indicate replace operations so we have to infer them) */
1717 if (!equation->priv->in_delete)
1718 math_equation_push_undo_stack(equation);
1720 /* Clear result on next digit entered if cursor at end of line */
1721 // FIXME Cursor
1722 c = g_utf8_get_char(text);
1723 if ((g_unichar_isdigit(c) || c == mp_serializer_get_radix(equation->priv->serializer)) &&
1724 math_equation_is_result(equation)) {
1725 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), "", -1);
1726 clear_ans(equation, FALSE);
1727 gtk_text_buffer_get_end_iter(GTK_TEXT_BUFFER(equation), location);
1730 if (equation->priv->ans_start) {
1731 gint ans_start, ans_end;
1732 gint offset;
1734 offset = gtk_text_iter_get_offset(location);
1735 get_ans_offsets(equation, &ans_start, &ans_end);
1737 /* Inserted inside ans */
1738 if (offset > ans_start && offset < ans_end)
1739 clear_ans(equation, TRUE);
1744 static gboolean
1745 on_delete(MathEquation *equation)
1747 equation->priv->in_delete = FALSE;
1748 return FALSE;
1752 static void
1753 pre_delete_range_cb(MathEquation *equation,
1754 GtkTextIter *start,
1755 GtkTextIter *end,
1756 gpointer user_data)
1758 if (equation->priv->in_reformat)
1759 return;
1761 math_equation_push_undo_stack(equation);
1763 equation->priv->in_delete = TRUE;
1764 g_idle_add((GSourceFunc)on_delete, equation);
1766 if (equation->priv->ans_start) {
1767 gint ans_start, ans_end;
1768 gint start_offset, end_offset;
1770 start_offset = gtk_text_iter_get_offset(start);
1771 end_offset = gtk_text_iter_get_offset(end);
1772 get_ans_offsets(equation, &ans_start, &ans_end);
1774 /* Deleted part of ans */
1775 if (start_offset < ans_end && end_offset > ans_start)
1776 clear_ans(equation, TRUE);
1781 static void
1782 insert_text_cb(MathEquation *equation,
1783 GtkTextIter *location,
1784 gchar *text,
1785 gint len,
1786 gpointer user_data)
1788 if (equation->priv->in_reformat)
1789 return;
1791 equation->priv->state.entered_multiply = strcmp(text, "×") == 0;
1793 /* Update thousands separators */
1794 reformat_separators(equation);
1796 g_object_notify(G_OBJECT(equation), "display");
1800 static void
1801 delete_range_cb(MathEquation *equation,
1802 GtkTextIter *start,
1803 GtkTextIter *end,
1804 gpointer user_data)
1806 if (equation->priv->in_reformat)
1807 return;
1809 equation->priv->state.entered_multiply = FALSE;
1811 /* Update thousands separators */
1812 reformat_separators(equation);
1814 // FIXME: A replace will emit this both for delete-range and insert-text, can it be avoided?
1815 g_object_notify(G_OBJECT(equation), "display");
1819 static void
1820 math_equation_init(MathEquation *equation)
1822 /* Digits localized for the given language */
1823 const char *digit_values = _("0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F");
1824 const char *default_digits[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"};
1825 gchar **digits;
1826 gboolean use_default_digits = FALSE;
1827 int i;
1829 equation->priv = G_TYPE_INSTANCE_GET_PRIVATE(equation, math_equation_get_type(), MathEquationPrivate);
1831 g_signal_connect(equation, "insert-text", G_CALLBACK(pre_insert_text_cb), equation);
1832 g_signal_connect(equation, "delete-range", G_CALLBACK(pre_delete_range_cb), equation);
1833 g_signal_connect_after(equation, "insert-text", G_CALLBACK(insert_text_cb), equation);
1834 g_signal_connect_after(equation, "delete-range", G_CALLBACK(delete_range_cb), equation);
1836 digits = g_strsplit(digit_values, ",", -1);
1837 for (i = 0; i < 16; i++) {
1838 if (use_default_digits || digits[i] == NULL) {
1839 use_default_digits = TRUE;
1840 equation->priv->digits[i] = g_utf8_get_char(default_digits[i]);
1842 else
1843 equation->priv->digits[i] = g_utf8_get_char(digits[i]);
1845 g_strfreev(digits);
1847 equation->priv->variables = math_variables_new();
1848 equation->priv->unit_manager = unit_manager_get_default();
1850 equation->priv->state.status = g_strdup("");
1851 equation->priv->word_size = 32;
1852 equation->priv->angle_units = MP_DEGREES;
1853 // FIXME: Pick based on locale
1854 equation->priv->source_currency = g_strdup("");
1855 equation->priv->target_currency = g_strdup("");
1856 equation->priv->source_units = g_strdup("");
1857 equation->priv->target_units = g_strdup("");
1858 equation->priv->serializer = mp_serializer_new(MP_DISPLAY_FORMAT_AUTOMATIC, 10, 9);
1859 equation->priv->queue = g_async_queue_new();
1861 mp_set_from_integer(0, &equation->priv->state.ans);