Support entering super/subscript with numeric keypad (Bug #634904)
[gcalctool.git] / src / math-equation.c
blob0109a67c65d4bb6fc21b9fbb822052273016d25e
1 /*
2 * Copyright (C) 1987-2008 Sun Microsystems, Inc. All Rights Reserved.
3 * Copyright (C) 2008-2011 Robert Ancell
5 * This program is free software: you can redistribute it and/or modify it under
6 * the terms of the GNU General Public License as published by the Free Software
7 * Foundation, either version 2 of the License, or (at your option) any later
8 * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
9 * license.
12 #include <stdlib.h>
13 #include <stdio.h>
14 #include <string.h>
15 #include <ctype.h>
16 #include <math.h>
17 #include <errno.h>
18 #include <glib.h>
19 #include <glib/gi18n.h>
21 #include "math-equation.h"
23 #include "mp.h"
24 #include "mp-equation.h"
25 #include "mp-serializer.h"
26 #include "mp-enums.h"
27 #include "unit-manager.h"
30 enum {
31 PROP_0,
32 PROP_STATUS,
33 PROP_DISPLAY,
34 PROP_EQUATION,
35 PROP_NUMBER_MODE,
36 PROP_ACCURACY,
37 PROP_SHOW_THOUSANDS_SEPARATORS,
38 PROP_SHOW_TRAILING_ZEROES,
39 PROP_NUMBER_FORMAT,
40 PROP_BASE,
41 PROP_WORD_SIZE,
42 PROP_ANGLE_UNITS,
43 PROP_SOURCE_CURRENCY,
44 PROP_TARGET_CURRENCY,
45 PROP_SOURCE_UNITS,
46 PROP_TARGET_UNITS,
47 PROP_SERIALIZER
50 static GType number_mode_type, number_format_type, angle_unit_type;
52 #define MAX_DIGITS 512
54 /* Expression mode state */
55 typedef struct {
56 MPNumber ans; /* Previously calculated answer */
57 gchar *expression; /* Expression entered by user */
58 gint ans_start, ans_end; /* Start and end characters for ans variable in expression */
59 gint cursor; /* ??? */
60 NumberMode number_mode; /* ??? */
61 gboolean can_super_minus; /* TRUE if entering minus can generate a superscript minus */
62 gboolean entered_multiply; /* Last insert was a multiply character */
63 gchar *status; /* Equation status */
64 } MathEquationState;
66 struct MathEquationPrivate
68 GtkTextTag *ans_tag;
70 gint word_size; /* Word size in bits */
71 MPAngleUnit angle_units; /* Units for trigonometric functions */
72 char *source_currency;
73 char *target_currency;
74 char *source_units;
75 char *target_units;
76 NumberMode number_mode; /* ??? */
77 gboolean can_super_minus; /* TRUE if entering minus can generate a superscript minus */
79 gunichar digits[16]; /* Localized digits */
81 GtkTextMark *ans_start, *ans_end;
83 MathEquationState state; /* Equation state */
84 GList *undo_stack; /* History of expression mode states */
85 GList *redo_stack;
86 gboolean in_undo_operation;
88 gboolean in_reformat;
90 gboolean in_delete;
92 gboolean in_solve;
94 MathVariables *variables;
95 MpSerializer *serializer;
97 GAsyncQueue *queue;
100 typedef struct {
101 MPNumber *number_result;
102 gchar *text_result;
103 gchar *error;
104 } SolveData;
106 G_DEFINE_TYPE (MathEquation, math_equation, GTK_TYPE_TEXT_BUFFER);
109 MathEquation *
110 math_equation_new()
112 return g_object_new(math_equation_get_type(), NULL);
116 MathVariables *
117 math_equation_get_variables(MathEquation *equation)
119 return equation->priv->variables;
123 static void
124 get_ans_offsets(MathEquation *equation, gint *start, gint *end)
126 GtkTextIter iter;
128 if (!equation->priv->ans_start) {
129 *start = *end = -1;
130 return;
133 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, equation->priv->ans_start);
134 *start = gtk_text_iter_get_offset(&iter);
135 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, equation->priv->ans_end);
136 *end = gtk_text_iter_get_offset(&iter);
140 static void
141 reformat_ans(MathEquation *equation)
143 if (!equation->priv->ans_start)
144 return;
146 gchar *orig_ans_text;
147 gchar *ans_text;
148 GtkTextIter ans_start, ans_end;
150 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &ans_start, equation->priv->ans_start);
151 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &ans_end, equation->priv->ans_end);
152 orig_ans_text = gtk_text_buffer_get_text(GTK_TEXT_BUFFER(equation), &ans_start, &ans_end, FALSE);
153 ans_text = mp_serializer_to_string(equation->priv->serializer, &equation->priv->state.ans);
154 if (strcmp(orig_ans_text, ans_text) != 0) {
155 gint start;
157 equation->priv->in_undo_operation = TRUE;
158 equation->priv->in_reformat = TRUE;
160 start = gtk_text_iter_get_offset(&ans_start);
161 gtk_text_buffer_delete(GTK_TEXT_BUFFER(equation), &ans_start, &ans_end);
162 gtk_text_buffer_insert_with_tags(GTK_TEXT_BUFFER(equation), &ans_end, ans_text, -1, equation->priv->ans_tag, NULL);
164 /* There seems to be a bug in the marks as they alternate being the correct and incorrect ways. Reset them */
165 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &ans_start, start);
166 gtk_text_buffer_move_mark(GTK_TEXT_BUFFER(equation), equation->priv->ans_start, &ans_start);
167 gtk_text_buffer_move_mark(GTK_TEXT_BUFFER(equation), equation->priv->ans_end, &ans_end);
169 equation->priv->in_reformat = FALSE;
170 equation->priv->in_undo_operation = FALSE;
172 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &ans_start, equation->priv->ans_start);
173 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &ans_end, equation->priv->ans_end);
174 g_free(orig_ans_text);
175 g_free(ans_text);
179 static gint
180 count_digits(MathEquation *equation, const gchar *text)
182 const gchar *read_iter;
183 gint count = 0;
185 read_iter = text;
186 while (*read_iter != '\0') {
187 if (!g_unichar_isdigit(g_utf8_get_char(read_iter)))
188 return count;
190 read_iter = g_utf8_next_char(read_iter);
192 /* Allow a thousands separator between digits follow a digit */
193 if (g_utf8_get_char(read_iter) == mp_serializer_get_thousands_separator(equation->priv->serializer)) {
194 read_iter = g_utf8_next_char(read_iter);
195 if (!g_unichar_isdigit(g_utf8_get_char(read_iter)))
196 return count;
199 count++;
202 return count;
206 static void
207 reformat_separators(MathEquation *equation)
209 gchar *text, *read_iter;
210 gint ans_start, ans_end;
211 gint offset, digit_offset = 0;
212 gboolean in_number = FALSE, in_radix = FALSE, last_is_tsep = FALSE;
214 equation->priv->in_undo_operation = TRUE;
215 equation->priv->in_reformat = TRUE;
217 text = math_equation_get_display(equation);
218 get_ans_offsets(equation, &ans_start, &ans_end);
219 for (read_iter = text, offset = 0; *read_iter != '\0'; read_iter = g_utf8_next_char(read_iter), offset++) {
220 gunichar c;
221 gboolean expect_tsep;
223 /* See what digit this character is */
224 c = g_utf8_get_char(read_iter);
226 expect_tsep = mp_serializer_get_show_thousands_separators(equation->priv->serializer) &&
227 in_number && !in_radix && !last_is_tsep &&
228 digit_offset > 0 && digit_offset % mp_serializer_get_thousands_separator_count(equation->priv->serializer) == 0;
229 last_is_tsep = FALSE;
231 /* Don't mess with ans */
232 if (offset >= ans_start && offset <= ans_end) {
233 in_number = in_radix = FALSE;
234 continue;
236 if (g_unichar_isdigit(c)) {
237 if (!in_number)
238 digit_offset = count_digits(equation, read_iter);
239 in_number = TRUE;
241 /* Expected a thousands separator between these digits - insert it */
242 if (expect_tsep) {
243 GtkTextIter iter;
244 gchar buffer[7];
245 gint len;
247 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &iter, offset);
248 len = g_unichar_to_utf8(mp_serializer_get_thousands_separator(equation->priv->serializer), buffer);
249 buffer[len] = '\0';
250 gtk_text_buffer_insert(GTK_TEXT_BUFFER(equation), &iter, buffer, -1);
251 offset++;
252 last_is_tsep = TRUE;
255 digit_offset--;
257 else if (c == mp_serializer_get_radix(equation->priv->serializer)) {
258 in_number = in_radix = TRUE;
260 else if (c == mp_serializer_get_thousands_separator(equation->priv->serializer)) {
261 /* Didn't expect thousands separator - delete it */
262 if (!expect_tsep && in_number) {
263 GtkTextIter start, end;
264 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &start, offset);
265 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &end, offset + 1);
266 gtk_text_buffer_delete(GTK_TEXT_BUFFER(equation), &start, &end);
267 offset--;
269 else
270 last_is_tsep = TRUE;
272 else {
273 in_number = in_radix = FALSE;
277 g_free(text);
279 equation->priv->in_reformat = FALSE;
280 equation->priv->in_undo_operation = FALSE;
284 static void
285 reformat_display(MathEquation *equation, gint old_base)
287 /* Change ans */
288 reformat_ans(equation);
290 /* Add/remove thousands separators */
291 reformat_separators(equation);
295 static MathEquationState *
296 get_current_state(MathEquation *equation)
298 MathEquationState *state;
299 gint ans_start = -1, ans_end = -1;
301 state = g_malloc0(sizeof(MathEquationState));
303 if (equation->priv->ans_start)
305 GtkTextIter iter;
306 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, equation->priv->ans_start);
307 ans_start = gtk_text_iter_get_offset(&iter);
308 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, equation->priv->ans_end);
309 ans_end = gtk_text_iter_get_offset(&iter);
312 mp_set_from_mp(&equation->priv->state.ans, &state->ans);
313 state->expression = math_equation_get_display(equation);
314 state->ans_start = ans_start;
315 state->ans_end = ans_end;
316 g_object_get(G_OBJECT(equation), "cursor-position", &state->cursor, NULL);
317 state->number_mode = equation->priv->number_mode;
318 state->can_super_minus = equation->priv->can_super_minus;
319 state->entered_multiply = equation->priv->state.entered_multiply;
320 state->status = g_strdup(equation->priv->state.status);
322 return state;
326 static void
327 free_state(MathEquationState *state)
329 g_free(state->expression);
330 g_free(state->status);
331 g_free(state);
335 static void
336 math_equation_push_undo_stack(MathEquation *equation)
338 GList *link;
339 MathEquationState *state;
341 if (equation->priv->in_undo_operation)
342 return;
344 math_equation_set_status(equation, "");
346 /* Can't redo anymore */
347 for (link = equation->priv->redo_stack; link; link = link->next) {
348 state = link->data;
349 free_state(state);
351 g_list_free(equation->priv->redo_stack);
352 equation->priv->redo_stack = NULL;
354 state = get_current_state(equation);
355 equation->priv->undo_stack = g_list_prepend(equation->priv->undo_stack, state);
359 static void
360 clear_ans(MathEquation *equation, gboolean remove_tag)
362 if (!equation->priv->ans_start)
363 return;
365 if (remove_tag) {
366 GtkTextIter start, end;
368 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &start, equation->priv->ans_start);
369 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &end, equation->priv->ans_end);
370 gtk_text_buffer_remove_tag(GTK_TEXT_BUFFER(equation), equation->priv->ans_tag, &start, &end);
373 gtk_text_buffer_delete_mark(GTK_TEXT_BUFFER(equation), equation->priv->ans_start);
374 gtk_text_buffer_delete_mark(GTK_TEXT_BUFFER(equation), equation->priv->ans_end);
375 equation->priv->ans_start = NULL;
376 equation->priv->ans_end = NULL;
380 static void
381 apply_state(MathEquation *equation, MathEquationState *state)
383 GtkTextIter cursor;
385 /* Disable undo detection */
386 equation->priv->in_undo_operation = TRUE;
388 mp_set_from_mp(&state->ans, &equation->priv->state.ans);
390 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), state->expression, -1);
391 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &cursor, state->cursor);
392 gtk_text_buffer_place_cursor(GTK_TEXT_BUFFER(equation), &cursor);
393 clear_ans(equation, FALSE);
394 if (state->ans_start >= 0) {
395 GtkTextIter start, end;
397 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &start, state->ans_start);
398 equation->priv->ans_start = gtk_text_buffer_create_mark(GTK_TEXT_BUFFER(equation), NULL, &start, FALSE);
399 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &end, state->ans_end);
400 equation->priv->ans_end = gtk_text_buffer_create_mark(GTK_TEXT_BUFFER(equation), NULL, &end, TRUE);
401 gtk_text_buffer_apply_tag(GTK_TEXT_BUFFER(equation), equation->priv->ans_tag, &start, &end);
404 math_equation_set_number_mode(equation, state->number_mode);
405 equation->priv->can_super_minus = state->can_super_minus;
406 equation->priv->state.entered_multiply = state->entered_multiply;
407 math_equation_set_status(equation, state->status);
409 equation->priv->in_undo_operation = FALSE;
413 void
414 math_equation_copy(MathEquation *equation)
416 GtkTextIter start, end;
417 gchar *text;
419 if (!gtk_text_buffer_get_selection_bounds(GTK_TEXT_BUFFER(equation), &start, &end))
420 gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(equation), &start, &end);
422 text = gtk_text_buffer_get_text(GTK_TEXT_BUFFER(equation), &start, &end, FALSE);
423 gtk_clipboard_set_text(gtk_clipboard_get(GDK_NONE), text, -1);
424 g_free(text);
428 static void
429 on_paste(GtkClipboard *clipboard, const gchar *text, gpointer data)
431 MathEquation *equation = data;
432 if (text != NULL)
433 math_equation_insert(equation, text);
437 void
438 math_equation_paste(MathEquation *equation)
440 gtk_clipboard_request_text(gtk_clipboard_get(GDK_NONE), on_paste, equation);
444 void
445 math_equation_undo(MathEquation *equation)
447 GList *link;
448 MathEquationState *state;
450 if (!equation->priv->undo_stack) {
451 math_equation_set_status(equation,
452 /* Error shown when trying to undo with no undo history */
453 _("No undo history"));
454 return;
457 link = equation->priv->undo_stack;
458 equation->priv->undo_stack = g_list_remove_link(equation->priv->undo_stack, link);
459 state = link->data;
460 g_list_free(link);
462 equation->priv->redo_stack = g_list_prepend(equation->priv->redo_stack, get_current_state(equation));
464 apply_state(equation, state);
465 free_state(state);
469 void
470 math_equation_redo(MathEquation *equation)
472 GList *link;
473 MathEquationState *state;
475 if (!equation->priv->redo_stack) {
476 math_equation_set_status(equation,
477 /* Error shown when trying to redo with no redo history */
478 _("No redo history"));
479 return;
482 link = equation->priv->redo_stack;
483 equation->priv->redo_stack = g_list_remove_link(equation->priv->redo_stack, link);
484 state = link->data;
485 g_list_free(link);
487 equation->priv->undo_stack = g_list_prepend(equation->priv->undo_stack, get_current_state(equation));
489 apply_state(equation, state);
490 free_state(state);
494 gunichar
495 math_equation_get_digit_text(MathEquation *equation, guint digit)
497 return equation->priv->digits[digit];
501 void
502 math_equation_set_accuracy(MathEquation *equation, gint accuracy)
504 gint old_accuracy = mp_serializer_get_accuracy(equation->priv->serializer);
505 if (old_accuracy == accuracy)
506 return;
507 mp_serializer_set_accuracy(equation->priv->serializer, accuracy);
508 reformat_display(equation, mp_serializer_get_base(equation->priv->serializer));
509 g_object_notify(G_OBJECT(equation), "accuracy");
513 gint
514 math_equation_get_accuracy(MathEquation *equation)
516 return mp_serializer_get_accuracy(equation->priv->serializer);
520 void
521 math_equation_set_show_thousands_separators(MathEquation *equation, gboolean visible)
523 gboolean old_visible = mp_serializer_get_show_thousands_separators(equation->priv->serializer);
524 if (old_visible == visible)
525 return;
526 mp_serializer_set_show_thousands_separators(equation->priv->serializer, visible);
527 reformat_display(equation, mp_serializer_get_base(equation->priv->serializer));
528 g_object_notify(G_OBJECT(equation), "show-thousands-separators");
532 gboolean
533 math_equation_get_show_thousands_separators(MathEquation *equation)
535 return mp_serializer_get_show_thousands_separators(equation->priv->serializer);
539 void
540 math_equation_set_show_trailing_zeroes(MathEquation *equation, gboolean visible)
542 gboolean old_visible = mp_serializer_get_show_trailing_zeroes(equation->priv->serializer);
543 if (old_visible == visible)
544 return;
545 mp_serializer_set_show_trailing_zeroes(equation->priv->serializer, visible);
546 reformat_display(equation, mp_serializer_get_base(equation->priv->serializer));
547 g_object_notify(G_OBJECT(equation), "show-trailing-zeroes");
551 gboolean
552 math_equation_get_show_trailing_zeroes(MathEquation *equation)
554 return mp_serializer_get_show_trailing_zeroes(equation->priv->serializer);
558 void
559 math_equation_set_number_format(MathEquation *equation, MpDisplayFormat format)
561 MpDisplayFormat old_format = mp_serializer_get_number_format(equation->priv->serializer);
562 if (old_format == format)
563 return;
565 mp_serializer_set_number_format(equation->priv->serializer, format);
566 reformat_display(equation, mp_serializer_get_base(equation->priv->serializer));
567 g_object_notify(G_OBJECT(equation), "number-format");
571 MpDisplayFormat
572 math_equation_get_number_format(MathEquation *equation)
574 return mp_serializer_get_number_format(equation->priv->serializer);
578 void
579 math_equation_set_base(MathEquation *equation, gint base)
581 gint old_base = mp_serializer_get_base(equation->priv->serializer);
583 if (old_base == base)
584 return;
586 mp_serializer_set_base(equation->priv->serializer, base);
587 reformat_display(equation, old_base);
588 g_object_notify(G_OBJECT(equation), "base");
592 gint
593 math_equation_get_base(MathEquation *equation)
595 return mp_serializer_get_base(equation->priv->serializer);
599 void
600 math_equation_set_word_size(MathEquation *equation, gint word_size)
602 if (equation->priv->word_size == word_size)
603 return;
604 equation->priv->word_size = word_size;
605 g_object_notify(G_OBJECT(equation), "word-size");
609 gint
610 math_equation_get_word_size(MathEquation *equation)
612 return equation->priv->word_size;
616 void
617 math_equation_set_angle_units(MathEquation *equation, MPAngleUnit angle_units)
619 if (equation->priv->angle_units == angle_units)
620 return;
621 equation->priv->angle_units = angle_units;
622 g_object_notify(G_OBJECT(equation), "angle-units");
626 MPAngleUnit
627 math_equation_get_angle_units(MathEquation *equation)
629 return equation->priv->angle_units;
633 void
634 math_equation_set_source_currency(MathEquation *equation, const gchar *currency)
636 if (strcmp(equation->priv->source_currency, currency) == 0)
637 return;
638 g_free(equation->priv->source_currency);
639 equation->priv->source_currency = g_strdup(currency);
640 g_object_notify(G_OBJECT(equation), "source-currency");
644 const gchar *
645 math_equation_get_source_currency(MathEquation *equation)
647 return equation->priv->source_currency;
651 void
652 math_equation_set_target_currency(MathEquation *equation, const gchar *currency)
654 if (strcmp(equation->priv->target_currency, currency) == 0)
655 return;
656 g_free(equation->priv->target_currency);
657 equation->priv->target_currency = g_strdup(currency);
658 g_object_notify(G_OBJECT(equation), "target-currency");
662 const gchar *
663 math_equation_get_target_currency(MathEquation *equation)
665 return equation->priv->target_currency;
669 void
670 math_equation_set_source_units(MathEquation *equation, const gchar *units)
672 if (strcmp(equation->priv->source_units, units) == 0)
673 return;
674 g_free(equation->priv->source_units);
675 equation->priv->source_units = g_strdup(units);
676 g_object_notify(G_OBJECT(equation), "source-units");
679 const gchar *
680 math_equation_get_source_units(MathEquation *equation)
682 return equation->priv->source_units;
686 void
687 math_equation_set_target_units(MathEquation *equation, const gchar *units)
689 if (strcmp(equation->priv->target_units, units) == 0)
690 return;
691 g_free(equation->priv->target_units);
692 equation->priv->target_units = g_strdup(units);
693 g_object_notify(G_OBJECT(equation), "target-units");
697 const gchar *
698 math_equation_get_target_units(MathEquation *equation)
700 return equation->priv->target_units;
704 void
705 math_equation_set_status(MathEquation *equation, const gchar *status)
707 if (strcmp(equation->priv->state.status, status) == 0)
708 return;
710 g_free(equation->priv->state.status);
711 equation->priv->state.status = g_strdup(status);
712 g_object_notify(G_OBJECT(equation), "status");
716 const gchar *
717 math_equation_get_status(MathEquation *equation)
719 return equation->priv->state.status;
723 gboolean
724 math_equation_is_empty(MathEquation *equation)
726 return gtk_text_buffer_get_char_count(GTK_TEXT_BUFFER(equation)) == 0;
730 gboolean
731 math_equation_is_result(MathEquation *equation)
733 char *text;
734 gboolean result;
736 text = math_equation_get_equation(equation);
737 result = strcmp(text, "ans") == 0;
738 g_free(text);
740 return result;
744 gchar *
745 math_equation_get_display(MathEquation *equation)
747 GtkTextIter start, end;
749 gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(equation), &start, &end);
750 return gtk_text_buffer_get_text(GTK_TEXT_BUFFER(equation), &start, &end, FALSE);
754 gchar *
755 math_equation_get_equation(MathEquation *equation)
757 gchar *text;
758 GString *eq_text;
759 gint ans_start = -1, ans_end = -1, offset;
760 const gchar *read_iter;
761 gboolean last_is_digit = FALSE;
763 text = math_equation_get_display(equation);
764 eq_text = g_string_sized_new(strlen(text));
766 if (equation->priv->ans_start)
767 get_ans_offsets(equation, &ans_start, &ans_end);
769 for (read_iter = text, offset = 0; *read_iter != '\0'; read_iter = g_utf8_next_char(read_iter), offset++) {
770 gunichar c;
771 gboolean is_digit, next_is_digit;
773 c = g_utf8_get_char(read_iter);
774 is_digit = g_unichar_isdigit(c);
775 next_is_digit = g_unichar_isdigit(g_utf8_get_char(g_utf8_next_char(read_iter)));
777 /* Replace ans text with variable */
778 if (offset == ans_start) {
779 g_string_append(eq_text, "ans");
780 read_iter = g_utf8_offset_to_pointer(read_iter, ans_end - ans_start - 1);
781 offset += ans_end - ans_start - 1;
782 is_digit = FALSE;
783 continue;
786 /* Ignore thousands separators */
787 if (c == mp_serializer_get_thousands_separator(equation->priv->serializer) && last_is_digit && next_is_digit)
789 /* Substitute radix character */
790 else if (c == mp_serializer_get_radix(equation->priv->serializer) && (last_is_digit || next_is_digit))
791 g_string_append_unichar(eq_text, '.');
792 else
793 g_string_append_unichar(eq_text, c);
795 last_is_digit = is_digit;
797 g_free(text);
799 text = eq_text->str;
800 g_string_free(eq_text, FALSE);
802 return text;
806 gboolean
807 math_equation_get_number(MathEquation *equation, MPNumber *z)
809 gchar *text;
810 gboolean result;
812 text = math_equation_get_equation(equation);
813 result = !mp_serializer_from_string(equation->priv->serializer, text, z);
814 g_free(text);
816 return result;
820 MpSerializer *
821 math_equation_get_serializer(MathEquation *equation)
823 return equation->priv->serializer;
827 void
828 math_equation_set_number_mode(MathEquation *equation, NumberMode mode)
830 if (equation->priv->number_mode == mode)
831 return;
833 equation->priv->can_super_minus = mode == SUPERSCRIPT;
835 equation->priv->number_mode = mode;
836 g_object_notify(G_OBJECT(equation), "number-mode");
840 NumberMode
841 math_equation_get_number_mode(MathEquation *equation)
843 return equation->priv->number_mode;
847 gboolean
848 math_equation_in_solve(MathEquation *equation)
850 return equation->priv->in_solve;
854 const MPNumber *
855 math_equation_get_answer(MathEquation *equation)
857 return &equation->priv->state.ans;
861 void
862 math_equation_store(MathEquation *equation, const gchar *name)
864 MPNumber t;
866 if (!math_equation_get_number(equation, &t))
867 math_equation_set_status(equation, _("No sane value to store"));
868 else
869 math_variables_set(equation->priv->variables, name, &t);
873 void
874 math_equation_recall(MathEquation *equation, const gchar *name)
876 math_equation_insert(equation, name);
880 void
881 math_equation_set(MathEquation *equation, const gchar *text)
884 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), text, -1);
885 clear_ans(equation, FALSE);
889 void
890 math_equation_set_number(MathEquation *equation, const MPNumber *x)
892 char *text;
893 GtkTextIter start, end;
895 /* Show the number in the user chosen format */
896 text = mp_serializer_to_string(equation->priv->serializer, x);
897 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), text, -1);
898 mp_set_from_mp(x, &equation->priv->state.ans);
900 /* Mark this text as the answer variable */
901 gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(equation), &start, &end);
902 clear_ans(equation, FALSE);
903 equation->priv->ans_start = gtk_text_buffer_create_mark(GTK_TEXT_BUFFER(equation), NULL, &start, FALSE);
904 equation->priv->ans_end = gtk_text_buffer_create_mark(GTK_TEXT_BUFFER(equation), NULL, &end, TRUE);
905 gtk_text_buffer_apply_tag(GTK_TEXT_BUFFER(equation), equation->priv->ans_tag, &start, &end);
906 g_free(text);
910 void
911 math_equation_insert(MathEquation *equation, const gchar *text)
913 /* Replace ** with ^ (not on all keyboards) */
914 if (!gtk_text_buffer_get_has_selection(GTK_TEXT_BUFFER(equation)) &&
915 strcmp(text, "×") == 0 && equation->priv->state.entered_multiply) {
916 GtkTextIter iter;
918 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, gtk_text_buffer_get_insert(GTK_TEXT_BUFFER(equation)));
919 gtk_text_buffer_backspace(GTK_TEXT_BUFFER(equation), &iter, TRUE, TRUE);
920 gtk_text_buffer_insert_at_cursor(GTK_TEXT_BUFFER(equation), "^", -1);
921 return;
924 /* Start new equation when entering digits after existing result */
925 if(math_equation_is_result(equation) && g_unichar_isdigit(g_utf8_get_char(text)))
926 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), "", -1);
928 /* Can't enter superscript minus after entering digits */
929 if (strstr("⁰¹²³⁴⁵⁶⁷⁸⁹", text) != NULL || strcmp("⁻", text) == 0)
930 equation->priv->can_super_minus = FALSE;
932 /* Disable super/subscript mode when finished entering */
933 if (strstr("⁻⁰¹²³⁴⁵⁶⁷⁸⁹₀₁₂₃₄₅₆₇₈₉", text) == NULL)
934 math_equation_set_number_mode(equation, NORMAL);
936 gtk_text_buffer_delete_selection(GTK_TEXT_BUFFER(equation), FALSE, FALSE);
937 gtk_text_buffer_insert_at_cursor(GTK_TEXT_BUFFER(equation), text, -1);
941 void
942 math_equation_insert_digit(MathEquation *equation, guint digit)
944 static const char *subscript_digits[] = {"₀", "₁", "₂", "₃", "₄", "₅", "₆", "₇", "₈", "₉", NULL};
945 static const char *superscript_digits[] = {"⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹", NULL};
947 if (equation->priv->number_mode == NORMAL || digit >= 10) {
948 gchar buffer[7];
949 gint len;
950 len = g_unichar_to_utf8(math_equation_get_digit_text(equation, digit), buffer);
951 buffer[len] = '\0';
952 math_equation_insert(equation, buffer);
954 else if (equation->priv->number_mode == SUPERSCRIPT)
955 math_equation_insert(equation, superscript_digits[digit]);
956 else if (equation->priv->number_mode == SUBSCRIPT)
957 math_equation_insert(equation, subscript_digits[digit]);
961 void
962 math_equation_insert_numeric_point(MathEquation *equation)
964 gchar buffer[7];
965 gint len;
966 len = g_unichar_to_utf8(mp_serializer_get_radix(equation->priv->serializer), buffer);
967 buffer[len] = '\0';
968 math_equation_insert(equation, buffer);
972 void
973 math_equation_insert_number(MathEquation *equation, const MPNumber *x)
975 char *text;
976 text = mp_serializer_to_string(equation->priv->serializer, x);
977 math_equation_insert(equation, text);
978 g_free(text);
982 void
983 math_equation_insert_exponent(MathEquation *equation)
985 math_equation_insert(equation, "×10");
986 math_equation_set_number_mode(equation, SUPERSCRIPT);
990 void
991 math_equation_insert_subtract(MathEquation *equation)
993 if (equation->priv->number_mode == SUPERSCRIPT && equation->priv->can_super_minus) {
994 math_equation_insert(equation, "⁻");
995 equation->priv->can_super_minus = FALSE;
997 else {
998 math_equation_insert(equation, "−");
999 math_equation_set_number_mode(equation, NORMAL);
1004 static int
1005 variable_is_defined(const char *name, void *data)
1007 MathEquation *equation = data;
1008 char *c, *lower_name;
1010 lower_name = strdup(name);
1011 for (c = lower_name; *c; c++)
1012 *c = tolower(*c);
1014 if (strcmp(lower_name, "rand") == 0 ||
1015 strcmp(lower_name, "ans") == 0) {
1016 g_free(lower_name);
1017 return 1;
1019 g_free(lower_name);
1021 return math_variables_get(equation->priv->variables, name) != NULL;
1025 static int
1026 get_variable(const char *name, MPNumber *z, void *data)
1028 char *c, *lower_name;
1029 int result = 1;
1030 MathEquation *equation = data;
1031 MPNumber *t;
1033 lower_name = strdup(name);
1034 for (c = lower_name; *c; c++)
1035 *c = tolower(*c);
1037 if (strcmp(lower_name, "rand") == 0)
1038 mp_set_from_random(z);
1039 else if (strcmp(lower_name, "ans") == 0)
1040 mp_set_from_mp(&equation->priv->state.ans, z);
1041 else {
1042 t = math_variables_get(equation->priv->variables, name);
1043 if (t)
1044 mp_set_from_mp(t, z);
1045 else
1046 result = 0;
1049 free(lower_name);
1051 return result;
1055 static void
1056 set_variable(const char *name, const MPNumber *x, void *data)
1058 MathEquation *equation = data;
1059 /* FIXME: Don't allow writing to built-in variables, e.g. ans, rand, sin, ... */
1060 math_variables_set(equation->priv->variables, name, x);
1064 static int
1065 convert(const MPNumber *x, const char *x_units, const char *z_units, MPNumber *z, void *data)
1067 return unit_manager_convert_by_symbol(unit_manager_get_default(), x, x_units, z_units, z);
1071 static int
1072 parse(MathEquation *equation, const char *text, MPNumber *z, char **error_token)
1074 MPEquationOptions options;
1076 memset(&options, 0, sizeof(options));
1077 options.base = mp_serializer_get_base(equation->priv->serializer);
1078 options.wordlen = equation->priv->word_size;
1079 options.angle_units = equation->priv->angle_units;
1080 options.variable_is_defined = variable_is_defined;
1081 options.get_variable = get_variable;
1082 options.set_variable = set_variable;
1083 options.convert = convert;
1084 options.callback_data = equation;
1086 return mp_equation_parse(text, &options, z, error_token);
1091 * Executed in separate thread. It is thus not a good idea to write to anything
1092 * in MathEquation but the async queue from here.
1094 static gpointer
1095 math_equation_solve_real(gpointer data)
1097 MathEquation *equation = MATH_EQUATION(data);
1098 SolveData *solvedata = g_slice_new0(SolveData);
1100 gint n_brackets = 0, result;
1101 gchar *c, *text, *error_token;
1102 GString *equation_text;
1103 MPNumber z;
1105 text = math_equation_get_equation(equation);
1106 equation_text = g_string_new(text);
1107 g_free(text);
1108 /* Count the number of brackets and automatically add missing closing brackets */
1109 for (c = equation_text->str; *c; c++) {
1110 if (*c == '(')
1111 n_brackets++;
1112 else if (*c == ')')
1113 n_brackets--;
1115 while (n_brackets > 0) {
1116 g_string_append_c(equation_text, ')');
1117 n_brackets--;
1121 result = parse(equation, equation_text->str, &z, &error_token);
1122 g_string_free(equation_text, TRUE);
1124 switch (result) {
1125 case PARSER_ERR_NONE:
1126 solvedata->number_result = g_slice_new(MPNumber);
1127 mp_set_from_mp(&z, solvedata->number_result);
1128 break;
1130 case PARSER_ERR_OVERFLOW:
1131 solvedata->error = g_strdup(/* Error displayed to user when they perform a bitwise operation on numbers greater than the current word */
1132 _("Overflow. Try a bigger word size"));
1133 break;
1135 case PARSER_ERR_UNKNOWN_VARIABLE:
1136 solvedata->error = g_strdup_printf(/* Error displayed to user when they an unknown variable is entered */
1137 _("Unknown variable '%s'"), error_token);
1138 break;
1140 case PARSER_ERR_UNKNOWN_FUNCTION:
1141 solvedata->error = g_strdup_printf(/* Error displayed to user when an unknown function is entered */
1142 _("Function '%s' is not defined"), error_token);
1143 break;
1145 case PARSER_ERR_UNKNOWN_CONVERSION:
1146 solvedata->error = g_strdup(/* Error displayed to user when an conversion with unknown units is attempted */
1147 _("Unknown conversion"));
1148 break;
1150 case PARSER_ERR_MP:
1151 solvedata->error = g_strdup(mp_get_error());
1152 break;
1154 default:
1155 solvedata->error = g_strdup(/* Error displayed to user when they enter an invalid calculation */
1156 _("Malformed expression"));
1157 break;
1159 g_async_queue_push(equation->priv->queue, solvedata);
1161 return NULL;
1165 static gboolean
1166 math_equation_show_in_progress(gpointer data)
1168 MathEquation *equation = MATH_EQUATION(data);
1169 if (equation->priv->in_solve)
1170 math_equation_set_status(equation, "Calculating");
1171 return false;
1175 static gboolean
1176 math_equation_look_for_answer(gpointer data)
1178 MathEquation *equation = MATH_EQUATION(data);
1179 SolveData *result = g_async_queue_try_pop(equation->priv->queue);
1181 if (result == NULL)
1182 return true;
1184 equation->priv->in_solve = false;
1186 if (!result->error)
1187 math_equation_set_status(equation, "");
1189 if (result->error != NULL) {
1190 math_equation_set_status(equation, result->error);
1191 g_free(result->error);
1193 else if (result->number_result != NULL) {
1194 math_equation_set_number(equation, result->number_result);
1195 g_slice_free(MPNumber, result->number_result);
1197 else if (result->text_result != NULL) {
1198 math_equation_set(equation, result->text_result);
1199 g_free(result->text_result);
1201 g_slice_free(SolveData, result);
1203 return false;
1207 void
1208 math_equation_solve(MathEquation *equation)
1210 GError *error = NULL;
1212 // FIXME: should replace calculation or give error message
1213 if (equation->priv->in_solve)
1214 return;
1216 if (math_equation_is_empty(equation))
1217 return;
1219 /* If showing a result return to the equation that caused it */
1220 // FIXME: Result may not be here due to solve (i.e. the user may have entered "ans")
1221 if (math_equation_is_result(equation)) {
1222 math_equation_undo(equation);
1223 return;
1226 equation->priv->in_solve = true;
1228 math_equation_set_number_mode(equation, NORMAL);
1230 g_thread_create(math_equation_solve_real, equation, false, &error);
1232 if (error)
1233 g_warning("Error spawning thread for calculations: %s\n", error->message);
1235 g_timeout_add(50, math_equation_look_for_answer, equation);
1236 g_timeout_add(100, math_equation_show_in_progress, equation);
1240 static gpointer
1241 math_equation_factorize_real(gpointer data)
1243 GString *text;
1244 GList *factors, *factor;
1245 MPNumber x;
1246 MathEquation *equation = MATH_EQUATION(data);
1247 SolveData *result = g_slice_new0(SolveData);
1249 math_equation_get_number(equation, &x);
1250 factors = mp_factorize(&x);
1252 text = g_string_new("");
1254 for (factor = factors; factor; factor = factor->next) {
1255 gchar *temp;
1256 MPNumber *n;
1258 n = factor->data;
1259 temp = mp_serializer_to_string(equation->priv->serializer, n);
1260 g_string_append(text, temp);
1261 if (factor->next)
1262 g_string_append(text, "×");
1263 g_slice_free(MPNumber, n);
1264 g_free(temp);
1266 g_list_free(factors);
1268 result->text_result = g_strndup(text->str, text->len);
1269 g_async_queue_push(equation->priv->queue, result);
1270 g_string_free(text, TRUE);
1272 return NULL;
1276 void
1277 math_equation_factorize(MathEquation *equation)
1279 MPNumber x;
1280 GError *error = NULL;
1282 // FIXME: should replace calculation or give error message
1283 if (equation->priv->in_solve)
1284 return;
1286 if (!math_equation_get_number(equation, &x) || !mp_is_integer(&x)) {
1287 /* Error displayed when trying to factorize a non-integer value */
1288 math_equation_set_status(equation, _("Need an integer to factorize"));
1289 return;
1292 equation->priv->in_solve = true;
1294 g_thread_create(math_equation_factorize_real, equation, false, &error);
1296 if (error)
1297 g_warning("Error spawning thread for calculations: %s\n", error->message);
1299 g_timeout_add(50, math_equation_look_for_answer, equation);
1300 g_timeout_add(100, math_equation_show_in_progress, equation);
1304 void
1305 math_equation_delete(MathEquation *equation)
1307 gint cursor;
1308 GtkTextIter start, end;
1310 g_object_get(G_OBJECT(equation), "cursor-position", &cursor, NULL);
1311 if (cursor >= gtk_text_buffer_get_char_count(GTK_TEXT_BUFFER(equation)))
1312 return;
1314 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &start, cursor);
1315 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &end, cursor+1);
1316 gtk_text_buffer_delete(GTK_TEXT_BUFFER(equation), &start, &end);
1320 void
1321 math_equation_backspace(MathEquation *equation)
1323 /* Can't delete empty display */
1324 if (math_equation_is_empty(equation))
1325 return;
1327 if (gtk_text_buffer_get_has_selection(GTK_TEXT_BUFFER(equation)))
1328 gtk_text_buffer_delete_selection(GTK_TEXT_BUFFER(equation), FALSE, FALSE);
1329 else {
1330 GtkTextIter iter;
1331 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, gtk_text_buffer_get_insert(GTK_TEXT_BUFFER(equation)));
1332 gtk_text_buffer_backspace(GTK_TEXT_BUFFER(equation), &iter, TRUE, TRUE);
1337 void
1338 math_equation_clear(MathEquation *equation)
1340 math_equation_set_number_mode(equation, NORMAL);
1341 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), "", -1);
1342 clear_ans(equation, FALSE);
1346 void
1347 math_equation_shift(MathEquation *equation, gint count)
1349 MPNumber z;
1351 if (!math_equation_get_number(equation, &z)) {
1352 math_equation_set_status(equation,
1353 /* This message is displayed in the status bar when a bit
1354 shift operation is performed and the display does not contain a number */
1355 _("No sane value to bitwise shift"));
1356 return;
1359 mp_shift(&z, count, &z);
1360 math_equation_set_number(equation, &z);
1364 void
1365 math_equation_toggle_bit(MathEquation *equation, guint bit)
1367 MPNumber x;
1368 guint64 bits;
1369 gboolean result;
1371 result = math_equation_get_number(equation, &x);
1372 if (result) {
1373 MPNumber max;
1374 mp_set_from_unsigned_integer(G_MAXUINT64, &max);
1375 if (mp_is_negative(&x) || mp_is_greater_than(&x, &max))
1376 result = FALSE;
1377 else
1378 bits = mp_cast_to_unsigned_int(&x);
1381 if (!result) {
1382 math_equation_set_status(equation,
1383 /* Message displayed when cannot toggle bit in display*/
1384 _("Displayed value not an integer"));
1385 return;
1388 bits ^= (1LL << (63 - bit));
1390 mp_set_from_unsigned_integer(bits, &x);
1392 // FIXME: Only do this if in ans format, otherwise set text in same format as previous number
1393 math_equation_set_number(equation, &x);
1397 static void
1398 math_equation_set_property(GObject *object,
1399 guint prop_id,
1400 const GValue *value,
1401 GParamSpec *pspec)
1403 MathEquation *self;
1405 self = MATH_EQUATION(object);
1407 switch (prop_id) {
1408 case PROP_STATUS:
1409 math_equation_set_status(self, g_value_get_string(value));
1410 break;
1411 case PROP_DISPLAY:
1412 math_equation_set(self, g_value_get_string(value));
1413 break;
1414 case PROP_NUMBER_MODE:
1415 math_equation_set_number_mode(self, g_value_get_int(value));
1416 break;
1417 case PROP_ACCURACY:
1418 math_equation_set_accuracy(self, g_value_get_int(value));
1419 break;
1420 case PROP_SHOW_THOUSANDS_SEPARATORS:
1421 math_equation_set_show_thousands_separators(self, g_value_get_boolean(value));
1422 break;
1423 case PROP_SHOW_TRAILING_ZEROES:
1424 math_equation_set_show_trailing_zeroes(self, g_value_get_boolean(value));
1425 break;
1426 case PROP_NUMBER_FORMAT:
1427 math_equation_set_number_format(self, g_value_get_int(value));
1428 break;
1429 case PROP_BASE:
1430 math_equation_set_base(self, g_value_get_int(value));
1431 break;
1432 case PROP_WORD_SIZE:
1433 math_equation_set_word_size(self, g_value_get_int(value));
1434 break;
1435 case PROP_ANGLE_UNITS:
1436 math_equation_set_angle_units(self, g_value_get_int(value));
1437 break;
1438 case PROP_SOURCE_CURRENCY:
1439 math_equation_set_source_currency(self, g_value_get_string(value));
1440 break;
1441 case PROP_TARGET_CURRENCY:
1442 math_equation_set_target_currency(self, g_value_get_string(value));
1443 break;
1444 case PROP_SOURCE_UNITS:
1445 math_equation_set_source_units(self, g_value_get_string(value));
1446 break;
1447 case PROP_TARGET_UNITS:
1448 math_equation_set_target_units(self, g_value_get_string(value));
1449 break;
1450 default:
1451 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
1452 break;
1457 static void
1458 math_equation_get_property(GObject *object,
1459 guint prop_id,
1460 GValue *value,
1461 GParamSpec *pspec)
1463 MathEquation *self;
1464 gchar *text;
1466 self = MATH_EQUATION(object);
1468 switch (prop_id) {
1469 case PROP_STATUS:
1470 g_value_set_string(value, self->priv->state.status);
1471 break;
1472 case PROP_DISPLAY:
1473 text = math_equation_get_display(self);
1474 g_value_set_string(value, text);
1475 g_free(text);
1476 break;
1477 case PROP_EQUATION:
1478 text = math_equation_get_equation(self);
1479 g_value_set_string(value, text);
1480 g_free(text);
1481 break;
1482 case PROP_NUMBER_MODE:
1483 g_value_set_enum(value, self->priv->number_mode);
1484 break;
1485 case PROP_ACCURACY:
1486 g_value_set_int(value, mp_serializer_get_accuracy(self->priv->serializer));
1487 break;
1488 case PROP_SHOW_THOUSANDS_SEPARATORS:
1489 g_value_set_boolean(value, mp_serializer_get_show_thousands_separators(self->priv->serializer));
1490 break;
1491 case PROP_SHOW_TRAILING_ZEROES:
1492 g_value_set_boolean(value, mp_serializer_get_show_trailing_zeroes(self->priv->serializer));
1493 break;
1494 case PROP_NUMBER_FORMAT:
1495 g_value_set_enum(value, mp_serializer_get_number_format(self->priv->serializer));
1496 break;
1497 case PROP_BASE:
1498 g_value_set_int(value, math_equation_get_base(self));
1499 break;
1500 case PROP_WORD_SIZE:
1501 g_value_set_int(value, self->priv->word_size);
1502 break;
1503 case PROP_ANGLE_UNITS:
1504 g_value_set_enum(value, self->priv->angle_units);
1505 break;
1506 case PROP_SOURCE_CURRENCY:
1507 g_value_set_string(value, self->priv->source_currency);
1508 break;
1509 case PROP_TARGET_CURRENCY:
1510 g_value_set_string(value, self->priv->target_currency);
1511 break;
1512 case PROP_SOURCE_UNITS:
1513 g_value_set_string(value, self->priv->source_units);
1514 break;
1515 case PROP_TARGET_UNITS:
1516 g_value_set_string(value, self->priv->target_units);
1517 break;
1518 case PROP_SERIALIZER:
1519 g_value_set_object(value, self->priv->serializer);
1520 break;
1521 default:
1522 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
1523 break;
1528 static void
1529 math_equation_constructed(GObject *object)
1531 GtkTextBuffer *parent_class;
1532 parent_class = g_type_class_peek_parent(MATH_EQUATION_GET_CLASS(object));
1533 if (G_OBJECT_CLASS(parent_class)->constructed)
1534 G_OBJECT_CLASS(parent_class)->constructed(object);
1536 MATH_EQUATION(object)->priv->ans_tag = gtk_text_buffer_create_tag(GTK_TEXT_BUFFER(object), NULL, "weight", PANGO_WEIGHT_BOLD, NULL);
1540 static void
1541 math_equation_class_init(MathEquationClass *klass)
1543 static GEnumValue number_mode_values[] =
1545 {NORMAL, "normal", "normal"},
1546 {SUPERSCRIPT, "superscript", "superscript"},
1547 {SUBSCRIPT, "subscript", "subscript"},
1548 {0, NULL, NULL}
1550 static GEnumValue angle_unit_values[] =
1552 {MP_RADIANS, "radians", "radians"},
1553 {MP_DEGREES, "degrees", "degrees"},
1554 {MP_GRADIANS, "gradians", "gradians"},
1555 {0, NULL, NULL}
1557 GObjectClass *object_class = G_OBJECT_CLASS(klass);
1559 object_class->get_property = math_equation_get_property;
1560 object_class->set_property = math_equation_set_property;
1561 object_class->constructed = math_equation_constructed;
1563 g_type_class_add_private(klass, sizeof(MathEquationPrivate));
1565 number_mode_type = g_enum_register_static("NumberMode", number_mode_values);
1566 number_format_type = math_mp_display_format_get_type();
1567 angle_unit_type = g_enum_register_static("AngleUnit", angle_unit_values);
1569 g_object_class_install_property(object_class,
1570 PROP_STATUS,
1571 g_param_spec_string("status",
1572 "status",
1573 "Equation status",
1575 G_PARAM_READWRITE));
1576 g_object_class_install_property(object_class,
1577 PROP_DISPLAY,
1578 g_param_spec_string("display",
1579 "display",
1580 "Displayed equation text",
1582 G_PARAM_READWRITE));
1583 g_object_class_install_property(object_class,
1584 PROP_EQUATION,
1585 g_param_spec_string("equation",
1586 "equation",
1587 "Equation text",
1589 G_PARAM_READABLE));
1590 g_object_class_install_property(object_class,
1591 PROP_NUMBER_MODE,
1592 g_param_spec_enum("number-mode",
1593 "number-mode",
1594 "Input number mode",
1595 number_mode_type,
1596 NORMAL,
1597 G_PARAM_READWRITE));
1598 g_object_class_install_property(object_class,
1599 PROP_ACCURACY,
1600 g_param_spec_int("accuracy",
1601 "accuracy",
1602 "Display accuracy",
1603 0, 20, 9,
1604 G_PARAM_READWRITE));
1605 g_object_class_install_property(object_class,
1606 PROP_SHOW_THOUSANDS_SEPARATORS,
1607 g_param_spec_boolean("show-thousands-separators",
1608 "show-thousands-separators",
1609 "Show thousands separators",
1610 TRUE,
1611 G_PARAM_READWRITE));
1612 g_object_class_install_property(object_class,
1613 PROP_SHOW_TRAILING_ZEROES,
1614 g_param_spec_boolean("show-trailing-zeroes",
1615 "show-trailing-zeroes",
1616 "Show trailing zeroes",
1617 FALSE,
1618 G_PARAM_READWRITE));
1619 g_object_class_install_property(object_class,
1620 PROP_NUMBER_FORMAT,
1621 g_param_spec_enum("number-format",
1622 "number-format",
1623 "Display format",
1624 number_format_type,
1625 MP_DISPLAY_FORMAT_FIXED,
1626 G_PARAM_READWRITE));
1627 g_object_class_install_property(object_class,
1628 PROP_BASE,
1629 g_param_spec_int("base",
1630 "base",
1631 "Default number base (derived from number-format)",
1632 2, 16, 10,
1633 G_PARAM_READWRITE));
1634 g_object_class_install_property(object_class,
1635 PROP_WORD_SIZE,
1636 g_param_spec_int("word-size",
1637 "word-size",
1638 "Word size in bits",
1639 8, 64, 64,
1640 G_PARAM_READWRITE));
1641 g_object_class_install_property(object_class,
1642 PROP_ANGLE_UNITS,
1643 g_param_spec_enum("angle-units",
1644 "angle-units",
1645 "Angle units",
1646 angle_unit_type,
1647 MP_DEGREES,
1648 G_PARAM_READWRITE));
1649 g_object_class_install_property(object_class,
1650 PROP_SOURCE_CURRENCY,
1651 g_param_spec_string("source-currency",
1652 "source-currency",
1653 "Source Currency",
1655 G_PARAM_READWRITE));
1656 g_object_class_install_property(object_class,
1657 PROP_TARGET_CURRENCY,
1658 g_param_spec_string("target-currency",
1659 "target-currency",
1660 "target Currency",
1662 G_PARAM_READWRITE));
1663 g_object_class_install_property(object_class,
1664 PROP_SOURCE_UNITS,
1665 g_param_spec_string("source-units",
1666 "source-units",
1667 "Source Units",
1669 G_PARAM_READWRITE));
1670 g_object_class_install_property(object_class,
1671 PROP_TARGET_UNITS,
1672 g_param_spec_string("target-units",
1673 "target-units",
1674 "target Units",
1676 G_PARAM_READWRITE));
1677 g_object_class_install_property(object_class,
1678 PROP_SERIALIZER,
1679 g_param_spec_object("serializer",
1680 "serializer",
1681 "Serializer",
1682 MP_TYPE_SERIALIZER,
1683 G_PARAM_READABLE));
1687 static void
1688 pre_insert_text_cb(MathEquation *equation,
1689 GtkTextIter *location,
1690 gchar *text,
1691 gint len,
1692 gpointer user_data)
1694 gunichar c;
1696 if (equation->priv->in_reformat)
1697 return;
1699 /* If following a delete then have already pushed undo stack (GtkTextBuffer
1700 doesn't indicate replace operations so we have to infer them) */
1701 if (!equation->priv->in_delete)
1702 math_equation_push_undo_stack(equation);
1704 /* Clear result on next digit entered if cursor at end of line */
1705 // FIXME Cursor
1706 c = g_utf8_get_char(text);
1707 if ((g_unichar_isdigit(c) || c == mp_serializer_get_radix(equation->priv->serializer)) &&
1708 math_equation_is_result(equation)) {
1709 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), "", -1);
1710 clear_ans(equation, FALSE);
1711 gtk_text_buffer_get_end_iter(GTK_TEXT_BUFFER(equation), location);
1714 if (equation->priv->ans_start) {
1715 gint ans_start, ans_end;
1716 gint offset;
1718 offset = gtk_text_iter_get_offset(location);
1719 get_ans_offsets(equation, &ans_start, &ans_end);
1721 /* Inserted inside ans */
1722 if (offset > ans_start && offset < ans_end)
1723 clear_ans(equation, TRUE);
1728 static gboolean
1729 on_delete(MathEquation *equation)
1731 equation->priv->in_delete = FALSE;
1732 return FALSE;
1736 static void
1737 pre_delete_range_cb(MathEquation *equation,
1738 GtkTextIter *start,
1739 GtkTextIter *end,
1740 gpointer user_data)
1742 if (equation->priv->in_reformat)
1743 return;
1745 math_equation_push_undo_stack(equation);
1747 equation->priv->in_delete = TRUE;
1748 g_idle_add((GSourceFunc)on_delete, equation);
1750 if (equation->priv->ans_start) {
1751 gint ans_start, ans_end;
1752 gint start_offset, end_offset;
1754 start_offset = gtk_text_iter_get_offset(start);
1755 end_offset = gtk_text_iter_get_offset(end);
1756 get_ans_offsets(equation, &ans_start, &ans_end);
1758 /* Deleted part of ans */
1759 if (start_offset < ans_end && end_offset > ans_start)
1760 clear_ans(equation, TRUE);
1765 static void
1766 insert_text_cb(MathEquation *equation,
1767 GtkTextIter *location,
1768 gchar *text,
1769 gint len,
1770 gpointer user_data)
1772 if (equation->priv->in_reformat)
1773 return;
1775 equation->priv->state.entered_multiply = strcmp(text, "×") == 0;
1777 /* Update thousands separators */
1778 reformat_separators(equation);
1780 g_object_notify(G_OBJECT(equation), "display");
1784 static void
1785 delete_range_cb(MathEquation *equation,
1786 GtkTextIter *start,
1787 GtkTextIter *end,
1788 gpointer user_data)
1790 if (equation->priv->in_reformat)
1791 return;
1793 equation->priv->state.entered_multiply = FALSE;
1795 /* Update thousands separators */
1796 reformat_separators(equation);
1798 // FIXME: A replace will emit this both for delete-range and insert-text, can it be avoided?
1799 g_object_notify(G_OBJECT(equation), "display");
1803 static void
1804 math_equation_init(MathEquation *equation)
1806 /* Digits localized for the given language */
1807 const char *digit_values = _("0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F");
1808 const char *default_digits[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"};
1809 gchar **digits;
1810 /* Default to using untranslated digits, this is because it doesn't make sense in most languages and we need to make this optional.
1811 * See https://bugzilla.gnome.org/show_bug.cgi?id=632661 */
1812 gboolean use_default_digits = TRUE;
1813 int i;
1815 equation->priv = G_TYPE_INSTANCE_GET_PRIVATE(equation, math_equation_get_type(), MathEquationPrivate);
1817 g_signal_connect(equation, "insert-text", G_CALLBACK(pre_insert_text_cb), equation);
1818 g_signal_connect(equation, "delete-range", G_CALLBACK(pre_delete_range_cb), equation);
1819 g_signal_connect_after(equation, "insert-text", G_CALLBACK(insert_text_cb), equation);
1820 g_signal_connect_after(equation, "delete-range", G_CALLBACK(delete_range_cb), equation);
1822 digits = g_strsplit(digit_values, ",", -1);
1823 for (i = 0; i < 16; i++) {
1824 if (use_default_digits || digits[i] == NULL) {
1825 use_default_digits = TRUE;
1826 equation->priv->digits[i] = g_utf8_get_char(default_digits[i]);
1828 else
1829 equation->priv->digits[i] = g_utf8_get_char(digits[i]);
1831 g_strfreev(digits);
1833 equation->priv->variables = math_variables_new();
1835 equation->priv->state.status = g_strdup("");
1836 equation->priv->word_size = 32;
1837 equation->priv->angle_units = MP_DEGREES;
1838 // FIXME: Pick based on locale
1839 equation->priv->source_currency = g_strdup("");
1840 equation->priv->target_currency = g_strdup("");
1841 equation->priv->source_units = g_strdup("");
1842 equation->priv->target_units = g_strdup("");
1843 equation->priv->serializer = mp_serializer_new(MP_DISPLAY_FORMAT_AUTOMATIC, 10, 9);
1844 equation->priv->queue = g_async_queue_new();
1846 mp_set_from_integer(0, &equation->priv->state.ans);