Updated Latvian translation and added Latvian translation for help by Viesturs Ružāns...
[gcalctool.git] / src / math-equation.c
blob6681ac126217acd86986fad1e4d2ffd3817e4404
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)
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 g_return_if_fail(equation != NULL);
421 if (!gtk_text_buffer_get_selection_bounds(GTK_TEXT_BUFFER(equation), &start, &end))
422 gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(equation), &start, &end);
424 text = gtk_text_buffer_get_text(GTK_TEXT_BUFFER(equation), &start, &end, FALSE);
425 gtk_clipboard_set_text(gtk_clipboard_get(GDK_NONE), text, -1);
426 g_free(text);
430 static void
431 on_paste(GtkClipboard *clipboard, const gchar *text, gpointer data)
433 MathEquation *equation = data;
434 if (text != NULL)
435 math_equation_insert(equation, text);
439 void
440 math_equation_paste(MathEquation *equation)
442 g_return_if_fail(equation != NULL);
443 gtk_clipboard_request_text(gtk_clipboard_get(GDK_NONE), on_paste, equation);
447 void
448 math_equation_undo(MathEquation *equation)
450 GList *link;
451 MathEquationState *state;
453 g_return_if_fail(equation != NULL);
455 if (!equation->priv->undo_stack) {
456 math_equation_set_status(equation,
457 /* Error shown when trying to undo with no undo history */
458 _("No undo history"));
459 return;
462 link = equation->priv->undo_stack;
463 equation->priv->undo_stack = g_list_remove_link(equation->priv->undo_stack, link);
464 state = link->data;
465 g_list_free(link);
467 equation->priv->redo_stack = g_list_prepend(equation->priv->redo_stack, get_current_state(equation));
469 apply_state(equation, state);
470 free_state(state);
474 void
475 math_equation_redo(MathEquation *equation)
477 GList *link;
478 MathEquationState *state;
480 g_return_if_fail(equation != NULL);
482 if (!equation->priv->redo_stack) {
483 math_equation_set_status(equation,
484 /* Error shown when trying to redo with no redo history */
485 _("No redo history"));
486 return;
489 link = equation->priv->redo_stack;
490 equation->priv->redo_stack = g_list_remove_link(equation->priv->redo_stack, link);
491 state = link->data;
492 g_list_free(link);
494 equation->priv->undo_stack = g_list_prepend(equation->priv->undo_stack, get_current_state(equation));
496 apply_state(equation, state);
497 free_state(state);
501 gunichar
502 math_equation_get_digit_text(MathEquation *equation, guint digit)
504 g_return_val_if_fail(equation != NULL, '?');
505 g_return_val_if_fail(digit < 16, '?');
507 return equation->priv->digits[digit];
511 void
512 math_equation_set_accuracy(MathEquation *equation, gint accuracy)
514 g_return_if_fail(equation != NULL);
516 if (mp_serializer_get_trailing_digits(equation->priv->serializer) == accuracy)
517 return;
518 mp_serializer_set_trailing_digits(equation->priv->serializer, accuracy);
519 reformat_display(equation);
520 g_object_notify(G_OBJECT(equation), "accuracy");
524 gint
525 math_equation_get_accuracy(MathEquation *equation)
527 g_return_val_if_fail(equation != NULL, 0);
529 return mp_serializer_get_trailing_digits(equation->priv->serializer);
533 void
534 math_equation_set_show_thousands_separators(MathEquation *equation, gboolean visible)
536 g_return_if_fail(equation != NULL);
538 if (mp_serializer_get_show_thousands_separators(equation->priv->serializer) == visible)
539 return;
541 mp_serializer_set_show_thousands_separators(equation->priv->serializer, visible);
542 reformat_display(equation);
543 g_object_notify(G_OBJECT(equation), "show-thousands-separators");
547 gboolean
548 math_equation_get_show_thousands_separators(MathEquation *equation)
550 g_return_val_if_fail(equation != NULL, FALSE);
551 return mp_serializer_get_show_thousands_separators(equation->priv->serializer);
555 void
556 math_equation_set_show_trailing_zeroes(MathEquation *equation, gboolean visible)
558 g_return_if_fail(equation != NULL);
560 if (mp_serializer_get_show_trailing_zeroes(equation->priv->serializer) == visible)
561 return;
563 mp_serializer_set_show_trailing_zeroes(equation->priv->serializer, visible);
564 reformat_display(equation);
565 g_object_notify(G_OBJECT(equation), "show-trailing-zeroes");
569 gboolean
570 math_equation_get_show_trailing_zeroes(MathEquation *equation)
572 g_return_val_if_fail(equation != NULL, FALSE);
573 return mp_serializer_get_show_trailing_zeroes(equation->priv->serializer);
577 void
578 math_equation_set_number_format(MathEquation *equation, MpDisplayFormat format)
580 g_return_if_fail(equation != NULL);
582 if (mp_serializer_get_number_format(equation->priv->serializer) == format)
583 return;
585 mp_serializer_set_number_format(equation->priv->serializer, format);
586 reformat_display(equation);
587 g_object_notify(G_OBJECT(equation), "number-format");
591 MpDisplayFormat
592 math_equation_get_number_format(MathEquation *equation)
594 g_return_val_if_fail(equation != NULL, MP_DISPLAY_FORMAT_AUTOMATIC);
595 return mp_serializer_get_number_format(equation->priv->serializer);
599 void
600 math_equation_set_base(MathEquation *equation, gint base)
602 g_return_if_fail(equation != NULL);
604 if (mp_serializer_get_base(equation->priv->serializer) == base)
605 return;
607 mp_serializer_set_base(equation->priv->serializer, base);
608 reformat_display(equation);
609 g_object_notify(G_OBJECT(equation), "base");
613 gint
614 math_equation_get_base(MathEquation *equation)
616 g_return_val_if_fail(equation != NULL, 10);
617 return mp_serializer_get_base(equation->priv->serializer);
621 void
622 math_equation_set_word_size(MathEquation *equation, gint word_size)
624 g_return_if_fail(equation != NULL);
626 if (equation->priv->word_size == word_size)
627 return;
629 equation->priv->word_size = word_size;
630 g_object_notify(G_OBJECT(equation), "word-size");
634 gint
635 math_equation_get_word_size(MathEquation *equation)
637 g_return_val_if_fail(equation != NULL, 64);
638 return equation->priv->word_size;
642 void
643 math_equation_set_angle_units(MathEquation *equation, MPAngleUnit angle_units)
645 g_return_if_fail(equation != NULL);
647 if (equation->priv->angle_units == angle_units)
648 return;
650 equation->priv->angle_units = angle_units;
651 g_object_notify(G_OBJECT(equation), "angle-units");
655 MPAngleUnit
656 math_equation_get_angle_units(MathEquation *equation)
658 g_return_val_if_fail(equation != NULL, MP_DEGREES);
659 return equation->priv->angle_units;
663 void
664 math_equation_set_source_currency(MathEquation *equation, const gchar *currency)
666 g_return_if_fail(equation != NULL);
667 g_return_if_fail(currency != NULL);
669 if (strcmp(equation->priv->source_currency, currency) == 0)
670 return;
671 g_free(equation->priv->source_currency);
672 equation->priv->source_currency = g_strdup(currency);
673 g_object_notify(G_OBJECT(equation), "source-currency");
677 const gchar *
678 math_equation_get_source_currency(MathEquation *equation)
680 g_return_val_if_fail(equation != NULL, NULL);
681 return equation->priv->source_currency;
685 void
686 math_equation_set_target_currency(MathEquation *equation, const gchar *currency)
688 g_return_if_fail(equation != NULL);
689 g_return_if_fail(currency != NULL);
691 if (strcmp(equation->priv->target_currency, currency) == 0)
692 return;
693 g_free(equation->priv->target_currency);
694 equation->priv->target_currency = g_strdup(currency);
695 g_object_notify(G_OBJECT(equation), "target-currency");
699 const gchar *
700 math_equation_get_target_currency(MathEquation *equation)
702 g_return_val_if_fail(equation != NULL, NULL);
703 return equation->priv->target_currency;
707 void
708 math_equation_set_source_units(MathEquation *equation, const gchar *units)
710 g_return_if_fail(equation != NULL);
711 g_return_if_fail(units != NULL);
713 if (strcmp(equation->priv->source_units, units) == 0)
714 return;
716 g_free(equation->priv->source_units);
717 equation->priv->source_units = g_strdup(units);
718 g_object_notify(G_OBJECT(equation), "source-units");
721 const gchar *
722 math_equation_get_source_units(MathEquation *equation)
724 g_return_val_if_fail(equation != NULL, NULL);
725 return equation->priv->source_units;
729 void
730 math_equation_set_target_units(MathEquation *equation, const gchar *units)
732 g_return_if_fail(equation != NULL);
733 g_return_if_fail(units != NULL);
735 if (strcmp(equation->priv->target_units, units) == 0)
736 return;
738 g_free(equation->priv->target_units);
739 equation->priv->target_units = g_strdup(units);
740 g_object_notify(G_OBJECT(equation), "target-units");
744 const gchar *
745 math_equation_get_target_units(MathEquation *equation)
747 g_return_val_if_fail(equation != NULL, NULL);
748 return equation->priv->target_units;
752 void
753 math_equation_set_status(MathEquation *equation, const gchar *status)
755 g_return_if_fail(equation != NULL);
756 g_return_if_fail(status != NULL);
758 if (strcmp(equation->priv->state.status, status) == 0)
759 return;
761 g_free(equation->priv->state.status);
762 equation->priv->state.status = g_strdup(status);
763 g_object_notify(G_OBJECT(equation), "status");
767 const gchar *
768 math_equation_get_status(MathEquation *equation)
770 g_return_val_if_fail(equation != NULL, NULL);
771 return equation->priv->state.status;
775 gboolean
776 math_equation_is_empty(MathEquation *equation)
778 g_return_val_if_fail(equation != NULL, FALSE);
779 return gtk_text_buffer_get_char_count(GTK_TEXT_BUFFER(equation)) == 0;
783 gboolean
784 math_equation_is_result(MathEquation *equation)
786 char *text;
787 gboolean result;
789 g_return_val_if_fail(equation != NULL, FALSE);
791 text = math_equation_get_equation(equation);
792 result = strcmp(text, "ans") == 0;
793 g_free(text);
795 return result;
799 gchar *
800 math_equation_get_display(MathEquation *equation)
802 GtkTextIter start, end;
804 g_return_val_if_fail(equation != NULL, NULL);
806 gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(equation), &start, &end);
807 return gtk_text_buffer_get_text(GTK_TEXT_BUFFER(equation), &start, &end, FALSE);
811 gchar *
812 math_equation_get_equation(MathEquation *equation)
814 gchar *text;
815 GString *eq_text;
816 gint ans_start = -1, ans_end = -1, offset;
817 const gchar *read_iter;
818 gboolean last_is_digit = FALSE;
820 g_return_val_if_fail(equation != NULL, NULL);
822 text = math_equation_get_display(equation);
823 eq_text = g_string_sized_new(strlen(text));
825 if (equation->priv->ans_start)
826 get_ans_offsets(equation, &ans_start, &ans_end);
828 for (read_iter = text, offset = 0; *read_iter != '\0'; read_iter = g_utf8_next_char(read_iter), offset++) {
829 gunichar c;
830 gboolean is_digit, next_is_digit;
832 c = g_utf8_get_char(read_iter);
833 is_digit = g_unichar_isdigit(c);
834 next_is_digit = g_unichar_isdigit(g_utf8_get_char(g_utf8_next_char(read_iter)));
836 /* Replace ans text with variable */
837 if (offset == ans_start) {
838 g_string_append(eq_text, "ans");
839 read_iter = g_utf8_offset_to_pointer(read_iter, ans_end - ans_start - 1);
840 offset += ans_end - ans_start - 1;
841 is_digit = FALSE;
842 continue;
845 /* Ignore thousands separators */
846 if (c == mp_serializer_get_thousands_separator(equation->priv->serializer) && last_is_digit && next_is_digit)
848 /* Substitute radix character */
849 else if (c == mp_serializer_get_radix(equation->priv->serializer) && (last_is_digit || next_is_digit))
850 g_string_append_unichar(eq_text, '.');
851 else
852 g_string_append_unichar(eq_text, c);
854 last_is_digit = is_digit;
856 g_free(text);
858 text = eq_text->str;
859 g_string_free(eq_text, FALSE);
861 return text;
865 gboolean
866 math_equation_get_number(MathEquation *equation, MPNumber *z)
868 gchar *text;
869 gboolean result;
871 g_return_val_if_fail(equation != NULL, FALSE);
872 g_return_val_if_fail(z != NULL, FALSE);
874 if (math_equation_is_result(equation)) {
875 mp_set_from_mp(math_equation_get_answer(equation), z);
876 return TRUE;
878 else {
879 text = math_equation_get_equation(equation);
880 result = !mp_serializer_from_string(equation->priv->serializer, text, z);
881 g_free(text);
882 return result;
887 MpSerializer *
888 math_equation_get_serializer(MathEquation *equation)
890 g_return_val_if_fail(equation != NULL, NULL);
891 return equation->priv->serializer;
895 void
896 math_equation_set_number_mode(MathEquation *equation, NumberMode mode)
898 g_return_if_fail(equation != NULL);
900 if (equation->priv->number_mode == mode)
901 return;
903 equation->priv->can_super_minus = mode == SUPERSCRIPT;
905 equation->priv->number_mode = mode;
906 g_object_notify(G_OBJECT(equation), "number-mode");
910 NumberMode
911 math_equation_get_number_mode(MathEquation *equation)
913 g_return_val_if_fail(equation != NULL, NORMAL);
914 return equation->priv->number_mode;
918 gboolean
919 math_equation_in_solve(MathEquation *equation)
921 g_return_val_if_fail(equation != NULL, FALSE);
922 return equation->priv->in_solve;
926 const MPNumber *
927 math_equation_get_answer(MathEquation *equation)
929 g_return_val_if_fail(equation != NULL, FALSE);
930 return &equation->priv->state.ans;
934 void
935 math_equation_store(MathEquation *equation, const gchar *name)
937 MPNumber t;
939 g_return_if_fail(equation != NULL);
940 g_return_if_fail(name != NULL);
942 if (!math_equation_get_number(equation, &t))
943 math_equation_set_status(equation, _("No sane value to store"));
944 else
945 math_variables_set(equation->priv->variables, name, &t);
949 void
950 math_equation_recall(MathEquation *equation, const gchar *name)
952 g_return_if_fail(equation != NULL);
953 g_return_if_fail(name != NULL);
954 math_equation_insert(equation, name);
958 void
959 math_equation_set(MathEquation *equation, const gchar *text)
961 g_return_if_fail(equation != NULL);
962 g_return_if_fail(text != NULL);
963 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), text, -1);
964 clear_ans(equation, FALSE);
968 void
969 math_equation_set_number(MathEquation *equation, const MPNumber *x)
971 char *text;
972 GtkTextIter start, end;
974 g_return_if_fail(equation != NULL);
975 g_return_if_fail(x != NULL);
977 /* Show the number in the user chosen format */
978 text = mp_serializer_to_string(equation->priv->serializer, x);
979 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), text, -1);
980 mp_set_from_mp(x, &equation->priv->state.ans);
982 /* Mark this text as the answer variable */
983 gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(equation), &start, &end);
984 clear_ans(equation, FALSE);
985 equation->priv->ans_start = gtk_text_buffer_create_mark(GTK_TEXT_BUFFER(equation), NULL, &start, FALSE);
986 equation->priv->ans_end = gtk_text_buffer_create_mark(GTK_TEXT_BUFFER(equation), NULL, &end, TRUE);
987 gtk_text_buffer_apply_tag(GTK_TEXT_BUFFER(equation), equation->priv->ans_tag, &start, &end);
988 g_free(text);
992 void
993 math_equation_insert(MathEquation *equation, const gchar *text)
995 g_return_if_fail(equation != NULL);
996 g_return_if_fail(text != NULL);
998 /* Replace ** with ^ (not on all keyboards) */
999 if (!gtk_text_buffer_get_has_selection(GTK_TEXT_BUFFER(equation)) &&
1000 strcmp(text, "×") == 0 && equation->priv->state.entered_multiply) {
1001 GtkTextIter iter;
1003 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, gtk_text_buffer_get_insert(GTK_TEXT_BUFFER(equation)));
1004 gtk_text_buffer_backspace(GTK_TEXT_BUFFER(equation), &iter, TRUE, TRUE);
1005 gtk_text_buffer_insert_at_cursor(GTK_TEXT_BUFFER(equation), "^", -1);
1006 return;
1009 /* Can't enter superscript minus after entering digits */
1010 if (strstr("⁰¹²³⁴⁵⁶⁷⁸⁹", text) != NULL || strcmp("⁻", text) == 0)
1011 equation->priv->can_super_minus = FALSE;
1013 /* Disable super/subscript mode when finished entering */
1014 if (strstr("⁻⁰¹²³⁴⁵⁶⁷⁸⁹₀₁₂₃₄₅₆₇₈₉", text) == NULL)
1015 math_equation_set_number_mode(equation, NORMAL);
1017 gtk_text_buffer_delete_selection(GTK_TEXT_BUFFER(equation), FALSE, FALSE);
1018 gtk_text_buffer_insert_at_cursor(GTK_TEXT_BUFFER(equation), text, -1);
1022 void
1023 math_equation_insert_digit(MathEquation *equation, guint digit)
1025 static const char *subscript_digits[] = {"₀", "₁", "₂", "₃", "₄", "₅", "₆", "₇", "₈", "₉", NULL};
1026 static const char *superscript_digits[] = {"⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹", NULL};
1028 g_return_if_fail(equation != NULL);
1029 g_return_if_fail(digit < 16);
1031 if (equation->priv->number_mode == NORMAL || digit >= 10) {
1032 gchar buffer[7];
1033 gint len;
1034 len = g_unichar_to_utf8(math_equation_get_digit_text(equation, digit), buffer);
1035 buffer[len] = '\0';
1036 math_equation_insert(equation, buffer);
1038 else if (equation->priv->number_mode == SUPERSCRIPT)
1039 math_equation_insert(equation, superscript_digits[digit]);
1040 else if (equation->priv->number_mode == SUBSCRIPT)
1041 math_equation_insert(equation, subscript_digits[digit]);
1045 void
1046 math_equation_insert_numeric_point(MathEquation *equation)
1048 gchar buffer[7];
1049 gint len;
1051 g_return_if_fail(equation != NULL);
1053 len = g_unichar_to_utf8(mp_serializer_get_radix(equation->priv->serializer), buffer);
1054 buffer[len] = '\0';
1055 math_equation_insert(equation, buffer);
1059 void
1060 math_equation_insert_number(MathEquation *equation, const MPNumber *x)
1062 char *text;
1064 g_return_if_fail(equation != NULL);
1065 g_return_if_fail(x != NULL);
1067 text = mp_serializer_to_string(equation->priv->serializer, x);
1068 math_equation_insert(equation, text);
1069 g_free(text);
1073 void
1074 math_equation_insert_exponent(MathEquation *equation)
1076 g_return_if_fail(equation != NULL);
1077 math_equation_insert(equation, "×10");
1078 math_equation_set_number_mode(equation, SUPERSCRIPT);
1082 void
1083 math_equation_insert_subtract(MathEquation *equation)
1085 g_return_if_fail(equation != NULL);
1086 if (equation->priv->number_mode == SUPERSCRIPT && equation->priv->can_super_minus) {
1087 math_equation_insert(equation, "⁻");
1088 equation->priv->can_super_minus = FALSE;
1090 else {
1091 math_equation_insert(equation, "−");
1092 math_equation_set_number_mode(equation, NORMAL);
1097 static int
1098 variable_is_defined(const char *name, void *data)
1100 MathEquation *equation = data;
1101 char *c, *lower_name;
1103 lower_name = strdup(name);
1104 for (c = lower_name; *c; c++)
1105 *c = tolower(*c);
1107 if (strcmp(lower_name, "rand") == 0 ||
1108 strcmp(lower_name, "ans") == 0) {
1109 g_free(lower_name);
1110 return 1;
1112 g_free(lower_name);
1114 return math_variables_get(equation->priv->variables, name) != NULL;
1118 static int
1119 get_variable(const char *name, MPNumber *z, void *data)
1121 char *c, *lower_name;
1122 int result = 1;
1123 MathEquation *equation = data;
1124 MPNumber *t;
1126 lower_name = strdup(name);
1127 for (c = lower_name; *c; c++)
1128 *c = tolower(*c);
1130 if (strcmp(lower_name, "rand") == 0)
1131 mp_set_from_random(z);
1132 else if (strcmp(lower_name, "ans") == 0)
1133 mp_set_from_mp(&equation->priv->state.ans, z);
1134 else {
1135 t = math_variables_get(equation->priv->variables, name);
1136 if (t)
1137 mp_set_from_mp(t, z);
1138 else
1139 result = 0;
1142 free(lower_name);
1144 return result;
1148 static void
1149 set_variable(const char *name, const MPNumber *x, void *data)
1151 MathEquation *equation = data;
1152 /* FIXME: Don't allow writing to built-in variables, e.g. ans, rand, sin, ... */
1153 math_variables_set(equation->priv->variables, name, x);
1157 static int
1158 convert(const MPNumber *x, const char *x_units, const char *z_units, MPNumber *z, void *data)
1160 return unit_manager_convert_by_symbol(unit_manager_get_default(), x, x_units, z_units, z);
1164 static int
1165 parse(MathEquation *equation, const char *text, MPNumber *z, char **error_token)
1167 MPEquationOptions options;
1169 memset(&options, 0, sizeof(options));
1170 options.base = mp_serializer_get_base(equation->priv->serializer);
1171 options.wordlen = equation->priv->word_size;
1172 options.angle_units = equation->priv->angle_units;
1173 options.variable_is_defined = variable_is_defined;
1174 options.get_variable = get_variable;
1175 options.set_variable = set_variable;
1176 options.convert = convert;
1177 options.callback_data = equation;
1179 return mp_equation_parse(text, &options, z, error_token);
1184 * Executed in separate thread. It is thus not a good idea to write to anything
1185 * in MathEquation but the async queue from here.
1187 static gpointer
1188 math_equation_solve_real(gpointer data)
1190 MathEquation *equation = MATH_EQUATION(data);
1191 SolveData *solvedata = g_slice_new0(SolveData);
1193 gint n_brackets = 0, result;
1194 gchar *c, *text, *error_token;
1195 GString *equation_text;
1196 MPNumber z;
1198 text = math_equation_get_equation(equation);
1199 equation_text = g_string_new(text);
1200 g_free(text);
1201 /* Count the number of brackets and automatically add missing closing brackets */
1202 for (c = equation_text->str; *c; c++) {
1203 if (*c == '(')
1204 n_brackets++;
1205 else if (*c == ')')
1206 n_brackets--;
1208 while (n_brackets > 0) {
1209 g_string_append_c(equation_text, ')');
1210 n_brackets--;
1214 result = parse(equation, equation_text->str, &z, &error_token);
1215 g_string_free(equation_text, TRUE);
1217 switch (result) {
1218 case PARSER_ERR_NONE:
1219 solvedata->number_result = g_slice_new(MPNumber);
1220 mp_set_from_mp(&z, solvedata->number_result);
1221 break;
1223 case PARSER_ERR_OVERFLOW:
1224 solvedata->error = g_strdup(/* Error displayed to user when they perform a bitwise operation on numbers greater than the current word */
1225 _("Overflow. Try a bigger word size"));
1226 break;
1228 case PARSER_ERR_UNKNOWN_VARIABLE:
1229 solvedata->error = g_strdup_printf(/* Error displayed to user when they an unknown variable is entered */
1230 _("Unknown variable '%s'"), error_token);
1231 break;
1233 case PARSER_ERR_UNKNOWN_FUNCTION:
1234 solvedata->error = g_strdup_printf(/* Error displayed to user when an unknown function is entered */
1235 _("Function '%s' is not defined"), error_token);
1236 break;
1238 case PARSER_ERR_UNKNOWN_CONVERSION:
1239 solvedata->error = g_strdup(/* Error displayed to user when an conversion with unknown units is attempted */
1240 _("Unknown conversion"));
1241 break;
1243 case PARSER_ERR_MP:
1244 solvedata->error = g_strdup(mp_get_error());
1245 break;
1247 default:
1248 solvedata->error = g_strdup(/* Error displayed to user when they enter an invalid calculation */
1249 _("Malformed expression"));
1250 break;
1252 g_async_queue_push(equation->priv->queue, solvedata);
1254 return NULL;
1258 static gboolean
1259 math_equation_show_in_progress(gpointer data)
1261 MathEquation *equation = MATH_EQUATION(data);
1262 if (equation->priv->in_solve)
1263 math_equation_set_status(equation, _("Calculating"));
1264 return false;
1268 static gboolean
1269 math_equation_look_for_answer(gpointer data)
1271 MathEquation *equation = MATH_EQUATION(data);
1272 SolveData *result = g_async_queue_try_pop(equation->priv->queue);
1274 if (result == NULL)
1275 return true;
1277 equation->priv->in_solve = false;
1279 if (!result->error)
1280 math_equation_set_status(equation, "");
1282 if (result->error != NULL) {
1283 math_equation_set_status(equation, result->error);
1284 g_free(result->error);
1286 else if (result->number_result != NULL) {
1287 math_equation_set_number(equation, result->number_result);
1288 g_slice_free(MPNumber, result->number_result);
1290 else if (result->text_result != NULL) {
1291 math_equation_set(equation, result->text_result);
1292 g_free(result->text_result);
1294 g_slice_free(SolveData, result);
1296 return false;
1300 void
1301 math_equation_solve(MathEquation *equation)
1303 g_return_if_fail(equation != NULL);
1305 // FIXME: should replace calculation or give error message
1306 if (equation->priv->in_solve)
1307 return;
1309 if (math_equation_is_empty(equation))
1310 return;
1312 /* If showing a result return to the equation that caused it */
1313 // FIXME: Result may not be here due to solve (i.e. the user may have entered "ans")
1314 if (math_equation_is_result(equation)) {
1315 math_equation_undo(equation);
1316 return;
1319 equation->priv->in_solve = true;
1321 math_equation_set_number_mode(equation, NORMAL);
1323 g_thread_new("", math_equation_solve_real, equation);
1325 g_timeout_add(50, math_equation_look_for_answer, equation);
1326 g_timeout_add(100, math_equation_show_in_progress, equation);
1330 static gpointer
1331 math_equation_factorize_real(gpointer data)
1333 GString *text;
1334 GList *factors, *factor;
1335 MPNumber x;
1336 MathEquation *equation = MATH_EQUATION(data);
1337 SolveData *result = g_slice_new0(SolveData);
1339 math_equation_get_number(equation, &x);
1340 factors = mp_factorize(&x);
1342 text = g_string_new("");
1344 for (factor = factors; factor; factor = factor->next) {
1345 gchar *temp;
1346 MPNumber *n;
1348 n = factor->data;
1349 temp = mp_serializer_to_string(equation->priv->serializer, n);
1350 g_string_append(text, temp);
1351 if (factor->next)
1352 g_string_append(text, "×");
1353 g_slice_free(MPNumber, n);
1354 g_free(temp);
1356 g_list_free(factors);
1358 result->text_result = g_strndup(text->str, text->len);
1359 g_async_queue_push(equation->priv->queue, result);
1360 g_string_free(text, TRUE);
1362 return NULL;
1366 void
1367 math_equation_factorize(MathEquation *equation)
1369 MPNumber x;
1371 g_return_if_fail(equation != NULL);
1373 // FIXME: should replace calculation or give error message
1374 if (equation->priv->in_solve)
1375 return;
1377 if (!math_equation_get_number(equation, &x) || !mp_is_integer(&x)) {
1378 /* Error displayed when trying to factorize a non-integer value */
1379 math_equation_set_status(equation, _("Need an integer to factorize"));
1380 return;
1383 equation->priv->in_solve = true;
1385 g_thread_new("", math_equation_factorize_real, equation);
1387 g_timeout_add(50, math_equation_look_for_answer, equation);
1388 g_timeout_add(100, math_equation_show_in_progress, equation);
1392 void
1393 math_equation_delete(MathEquation *equation)
1395 gint cursor;
1396 GtkTextIter start, end;
1398 g_return_if_fail(equation != NULL);
1400 g_object_get(G_OBJECT(equation), "cursor-position", &cursor, NULL);
1401 if (cursor >= gtk_text_buffer_get_char_count(GTK_TEXT_BUFFER(equation)))
1402 return;
1404 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &start, cursor);
1405 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &end, cursor+1);
1406 gtk_text_buffer_delete(GTK_TEXT_BUFFER(equation), &start, &end);
1410 void
1411 math_equation_backspace(MathEquation *equation)
1413 g_return_if_fail(equation != NULL);
1415 /* Can't delete empty display */
1416 if (math_equation_is_empty(equation))
1417 return;
1419 if (gtk_text_buffer_get_has_selection(GTK_TEXT_BUFFER(equation)))
1420 gtk_text_buffer_delete_selection(GTK_TEXT_BUFFER(equation), FALSE, FALSE);
1421 else {
1422 GtkTextIter iter;
1423 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, gtk_text_buffer_get_insert(GTK_TEXT_BUFFER(equation)));
1424 gtk_text_buffer_backspace(GTK_TEXT_BUFFER(equation), &iter, TRUE, TRUE);
1429 void
1430 math_equation_clear(MathEquation *equation)
1432 g_return_if_fail(equation != NULL);
1434 math_equation_set_number_mode(equation, NORMAL);
1435 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), "", -1);
1436 clear_ans(equation, FALSE);
1440 void
1441 math_equation_shift(MathEquation *equation, gint count)
1443 MPNumber z;
1445 g_return_if_fail(equation != NULL);
1447 if (!math_equation_get_number(equation, &z)) {
1448 math_equation_set_status(equation,
1449 /* This message is displayed in the status bar when a bit
1450 shift operation is performed and the display does not contain a number */
1451 _("No sane value to bitwise shift"));
1452 return;
1455 mp_shift(&z, count, &z);
1456 math_equation_set_number(equation, &z);
1460 void
1461 math_equation_toggle_bit(MathEquation *equation, guint bit)
1463 MPNumber x;
1464 guint64 bits;
1465 gboolean result;
1467 g_return_if_fail(equation != NULL);
1469 result = math_equation_get_number(equation, &x);
1470 if (result) {
1471 MPNumber max;
1472 mp_set_from_unsigned_integer(G_MAXUINT64, &max);
1473 if (mp_is_negative(&x) || mp_is_greater_than(&x, &max))
1474 result = FALSE;
1475 else
1476 bits = mp_cast_to_unsigned_int(&x);
1479 if (!result) {
1480 math_equation_set_status(equation,
1481 /* Message displayed when cannot toggle bit in display*/
1482 _("Displayed value not an integer"));
1483 return;
1486 bits ^= (1LL << (63 - bit));
1488 mp_set_from_unsigned_integer(bits, &x);
1490 // FIXME: Only do this if in ans format, otherwise set text in same format as previous number
1491 math_equation_set_number(equation, &x);
1495 static void
1496 math_equation_set_property(GObject *object,
1497 guint prop_id,
1498 const GValue *value,
1499 GParamSpec *pspec)
1501 MathEquation *self;
1503 self = MATH_EQUATION(object);
1505 switch (prop_id) {
1506 case PROP_STATUS:
1507 math_equation_set_status(self, g_value_get_string(value));
1508 break;
1509 case PROP_DISPLAY:
1510 math_equation_set(self, g_value_get_string(value));
1511 break;
1512 case PROP_NUMBER_MODE:
1513 math_equation_set_number_mode(self, g_value_get_int(value));
1514 break;
1515 case PROP_ACCURACY:
1516 math_equation_set_accuracy(self, g_value_get_int(value));
1517 break;
1518 case PROP_SHOW_THOUSANDS_SEPARATORS:
1519 math_equation_set_show_thousands_separators(self, g_value_get_boolean(value));
1520 break;
1521 case PROP_SHOW_TRAILING_ZEROES:
1522 math_equation_set_show_trailing_zeroes(self, g_value_get_boolean(value));
1523 break;
1524 case PROP_NUMBER_FORMAT:
1525 math_equation_set_number_format(self, g_value_get_int(value));
1526 break;
1527 case PROP_BASE:
1528 math_equation_set_base(self, g_value_get_int(value));
1529 break;
1530 case PROP_WORD_SIZE:
1531 math_equation_set_word_size(self, g_value_get_int(value));
1532 break;
1533 case PROP_ANGLE_UNITS:
1534 math_equation_set_angle_units(self, g_value_get_int(value));
1535 break;
1536 case PROP_SOURCE_CURRENCY:
1537 math_equation_set_source_currency(self, g_value_get_string(value));
1538 break;
1539 case PROP_TARGET_CURRENCY:
1540 math_equation_set_target_currency(self, g_value_get_string(value));
1541 break;
1542 case PROP_SOURCE_UNITS:
1543 math_equation_set_source_units(self, g_value_get_string(value));
1544 break;
1545 case PROP_TARGET_UNITS:
1546 math_equation_set_target_units(self, g_value_get_string(value));
1547 break;
1548 default:
1549 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
1550 break;
1555 static void
1556 math_equation_get_property(GObject *object,
1557 guint prop_id,
1558 GValue *value,
1559 GParamSpec *pspec)
1561 MathEquation *self;
1562 gchar *text;
1564 self = MATH_EQUATION(object);
1566 switch (prop_id) {
1567 case PROP_STATUS:
1568 g_value_set_string(value, self->priv->state.status);
1569 break;
1570 case PROP_DISPLAY:
1571 text = math_equation_get_display(self);
1572 g_value_set_string(value, text);
1573 g_free(text);
1574 break;
1575 case PROP_EQUATION:
1576 text = math_equation_get_equation(self);
1577 g_value_set_string(value, text);
1578 g_free(text);
1579 break;
1580 case PROP_NUMBER_MODE:
1581 g_value_set_enum(value, self->priv->number_mode);
1582 break;
1583 case PROP_ACCURACY:
1584 g_value_set_int(value, mp_serializer_get_trailing_digits(self->priv->serializer));
1585 break;
1586 case PROP_SHOW_THOUSANDS_SEPARATORS:
1587 g_value_set_boolean(value, mp_serializer_get_show_thousands_separators(self->priv->serializer));
1588 break;
1589 case PROP_SHOW_TRAILING_ZEROES:
1590 g_value_set_boolean(value, mp_serializer_get_show_trailing_zeroes(self->priv->serializer));
1591 break;
1592 case PROP_NUMBER_FORMAT:
1593 g_value_set_enum(value, mp_serializer_get_number_format(self->priv->serializer));
1594 break;
1595 case PROP_BASE:
1596 g_value_set_int(value, math_equation_get_base(self));
1597 break;
1598 case PROP_WORD_SIZE:
1599 g_value_set_int(value, self->priv->word_size);
1600 break;
1601 case PROP_ANGLE_UNITS:
1602 g_value_set_enum(value, self->priv->angle_units);
1603 break;
1604 case PROP_SOURCE_CURRENCY:
1605 g_value_set_string(value, self->priv->source_currency);
1606 break;
1607 case PROP_TARGET_CURRENCY:
1608 g_value_set_string(value, self->priv->target_currency);
1609 break;
1610 case PROP_SOURCE_UNITS:
1611 g_value_set_string(value, self->priv->source_units);
1612 break;
1613 case PROP_TARGET_UNITS:
1614 g_value_set_string(value, self->priv->target_units);
1615 break;
1616 case PROP_SERIALIZER:
1617 g_value_set_object(value, self->priv->serializer);
1618 break;
1619 default:
1620 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
1621 break;
1626 static void
1627 math_equation_constructed(GObject *object)
1629 GtkTextBuffer *parent_class;
1630 parent_class = g_type_class_peek_parent(MATH_EQUATION_GET_CLASS(object));
1631 if (G_OBJECT_CLASS(parent_class)->constructed)
1632 G_OBJECT_CLASS(parent_class)->constructed(object);
1634 MATH_EQUATION(object)->priv->ans_tag = gtk_text_buffer_create_tag(GTK_TEXT_BUFFER(object), NULL, "weight", PANGO_WEIGHT_BOLD, NULL);
1638 static void
1639 math_equation_class_init(MathEquationClass *klass)
1641 static GEnumValue number_mode_values[] =
1643 {NORMAL, "normal", "normal"},
1644 {SUPERSCRIPT, "superscript", "superscript"},
1645 {SUBSCRIPT, "subscript", "subscript"},
1646 {0, NULL, NULL}
1648 static GEnumValue angle_unit_values[] =
1650 {MP_RADIANS, "radians", "radians"},
1651 {MP_DEGREES, "degrees", "degrees"},
1652 {MP_GRADIANS, "gradians", "gradians"},
1653 {0, NULL, NULL}
1655 GObjectClass *object_class = G_OBJECT_CLASS(klass);
1657 object_class->get_property = math_equation_get_property;
1658 object_class->set_property = math_equation_set_property;
1659 object_class->constructed = math_equation_constructed;
1661 g_type_class_add_private(klass, sizeof(MathEquationPrivate));
1663 number_mode_type = g_enum_register_static("NumberMode", number_mode_values);
1664 number_format_type = math_mp_display_format_get_type();
1665 angle_unit_type = g_enum_register_static("AngleUnit", angle_unit_values);
1667 g_object_class_install_property(object_class,
1668 PROP_STATUS,
1669 g_param_spec_string("status",
1670 "status",
1671 "Equation status",
1673 G_PARAM_READWRITE));
1674 g_object_class_install_property(object_class,
1675 PROP_DISPLAY,
1676 g_param_spec_string("display",
1677 "display",
1678 "Displayed equation text",
1680 G_PARAM_READWRITE));
1681 g_object_class_install_property(object_class,
1682 PROP_EQUATION,
1683 g_param_spec_string("equation",
1684 "equation",
1685 "Equation text",
1687 G_PARAM_READABLE));
1688 g_object_class_install_property(object_class,
1689 PROP_NUMBER_MODE,
1690 g_param_spec_enum("number-mode",
1691 "number-mode",
1692 "Input number mode",
1693 number_mode_type,
1694 NORMAL,
1695 G_PARAM_READWRITE));
1696 g_object_class_install_property(object_class,
1697 PROP_ACCURACY,
1698 g_param_spec_int("accuracy",
1699 "accuracy",
1700 "Display accuracy",
1701 0, 20, 9,
1702 G_PARAM_READWRITE));
1703 g_object_class_install_property(object_class,
1704 PROP_SHOW_THOUSANDS_SEPARATORS,
1705 g_param_spec_boolean("show-thousands-separators",
1706 "show-thousands-separators",
1707 "Show thousands separators",
1708 TRUE,
1709 G_PARAM_READWRITE));
1710 g_object_class_install_property(object_class,
1711 PROP_SHOW_TRAILING_ZEROES,
1712 g_param_spec_boolean("show-trailing-zeroes",
1713 "show-trailing-zeroes",
1714 "Show trailing zeroes",
1715 FALSE,
1716 G_PARAM_READWRITE));
1717 g_object_class_install_property(object_class,
1718 PROP_NUMBER_FORMAT,
1719 g_param_spec_enum("number-format",
1720 "number-format",
1721 "Display format",
1722 number_format_type,
1723 MP_DISPLAY_FORMAT_FIXED,
1724 G_PARAM_READWRITE));
1725 g_object_class_install_property(object_class,
1726 PROP_BASE,
1727 g_param_spec_int("base",
1728 "base",
1729 "Default number base (derived from number-format)",
1730 2, 16, 10,
1731 G_PARAM_READWRITE));
1732 g_object_class_install_property(object_class,
1733 PROP_WORD_SIZE,
1734 g_param_spec_int("word-size",
1735 "word-size",
1736 "Word size in bits",
1737 8, 64, 64,
1738 G_PARAM_READWRITE));
1739 g_object_class_install_property(object_class,
1740 PROP_ANGLE_UNITS,
1741 g_param_spec_enum("angle-units",
1742 "angle-units",
1743 "Angle units",
1744 angle_unit_type,
1745 MP_DEGREES,
1746 G_PARAM_READWRITE));
1747 g_object_class_install_property(object_class,
1748 PROP_SOURCE_CURRENCY,
1749 g_param_spec_string("source-currency",
1750 "source-currency",
1751 "Source Currency",
1753 G_PARAM_READWRITE));
1754 g_object_class_install_property(object_class,
1755 PROP_TARGET_CURRENCY,
1756 g_param_spec_string("target-currency",
1757 "target-currency",
1758 "target Currency",
1760 G_PARAM_READWRITE));
1761 g_object_class_install_property(object_class,
1762 PROP_SOURCE_UNITS,
1763 g_param_spec_string("source-units",
1764 "source-units",
1765 "Source Units",
1767 G_PARAM_READWRITE));
1768 g_object_class_install_property(object_class,
1769 PROP_TARGET_UNITS,
1770 g_param_spec_string("target-units",
1771 "target-units",
1772 "target Units",
1774 G_PARAM_READWRITE));
1775 g_object_class_install_property(object_class,
1776 PROP_SERIALIZER,
1777 g_param_spec_object("serializer",
1778 "serializer",
1779 "Serializer",
1780 MP_TYPE_SERIALIZER,
1781 G_PARAM_READABLE));
1785 static void
1786 pre_insert_text_cb(MathEquation *equation,
1787 GtkTextIter *location,
1788 gchar *text,
1789 gint len,
1790 gpointer user_data)
1792 gunichar c;
1793 gint cursor;
1795 if (equation->priv->in_reformat)
1796 return;
1798 /* If following a delete then have already pushed undo stack (GtkTextBuffer
1799 doesn't indicate replace operations so we have to infer them) */
1800 if (!equation->priv->in_delete)
1801 math_equation_push_undo_stack(equation);
1803 /* Clear result on next digit entered if cursor at end of line */
1804 c = g_utf8_get_char(text);
1805 g_object_get(G_OBJECT(equation), "cursor-position", &cursor, NULL);
1806 if ((g_unichar_isdigit(c) || c == mp_serializer_get_radix(equation->priv->serializer)) &&
1807 math_equation_is_result(equation) &&
1808 cursor >= gtk_text_buffer_get_char_count(GTK_TEXT_BUFFER(equation))) {
1809 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), "", -1);
1810 clear_ans(equation, FALSE);
1811 gtk_text_buffer_get_end_iter(GTK_TEXT_BUFFER(equation), location);
1814 if (equation->priv->ans_start) {
1815 gint ans_start, ans_end;
1816 gint offset;
1818 offset = gtk_text_iter_get_offset(location);
1819 get_ans_offsets(equation, &ans_start, &ans_end);
1821 /* Inserted inside ans */
1822 if (offset > ans_start && offset < ans_end)
1823 clear_ans(equation, TRUE);
1828 static gboolean
1829 on_delete(MathEquation *equation)
1831 equation->priv->in_delete = FALSE;
1832 return FALSE;
1836 static void
1837 pre_delete_range_cb(MathEquation *equation,
1838 GtkTextIter *start,
1839 GtkTextIter *end,
1840 gpointer user_data)
1842 if (equation->priv->in_reformat)
1843 return;
1845 math_equation_push_undo_stack(equation);
1847 equation->priv->in_delete = TRUE;
1848 g_idle_add((GSourceFunc)on_delete, equation);
1850 if (equation->priv->ans_start) {
1851 gint ans_start, ans_end;
1852 gint start_offset, end_offset;
1854 start_offset = gtk_text_iter_get_offset(start);
1855 end_offset = gtk_text_iter_get_offset(end);
1856 get_ans_offsets(equation, &ans_start, &ans_end);
1858 /* Deleted part of ans */
1859 if (start_offset < ans_end && end_offset > ans_start)
1860 clear_ans(equation, TRUE);
1865 static void
1866 insert_text_cb(MathEquation *equation,
1867 GtkTextIter *location,
1868 gchar *text,
1869 gint len,
1870 gpointer user_data)
1872 if (equation->priv->in_reformat)
1873 return;
1875 equation->priv->state.entered_multiply = strcmp(text, "×") == 0;
1877 /* Update thousands separators */
1878 reformat_separators(equation);
1880 g_object_notify(G_OBJECT(equation), "display");
1884 static void
1885 delete_range_cb(MathEquation *equation,
1886 GtkTextIter *start,
1887 GtkTextIter *end,
1888 gpointer user_data)
1890 if (equation->priv->in_reformat)
1891 return;
1893 equation->priv->state.entered_multiply = FALSE;
1895 /* Update thousands separators */
1896 reformat_separators(equation);
1898 // FIXME: A replace will emit this both for delete-range and insert-text, can it be avoided?
1899 g_object_notify(G_OBJECT(equation), "display");
1903 static void
1904 math_equation_init(MathEquation *equation)
1906 /* Digits localized for the given language */
1907 const char *digit_values = _("0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F");
1908 const char *default_digits[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"};
1909 gchar **digits;
1910 /* Default to using untranslated digits, this is because it doesn't make sense in most languages and we need to make this optional.
1911 * See https://bugzilla.gnome.org/show_bug.cgi?id=632661 */
1912 gboolean use_default_digits = TRUE;
1913 int i;
1915 equation->priv = G_TYPE_INSTANCE_GET_PRIVATE(equation, math_equation_get_type(), MathEquationPrivate);
1917 g_signal_connect(equation, "insert-text", G_CALLBACK(pre_insert_text_cb), equation);
1918 g_signal_connect(equation, "delete-range", G_CALLBACK(pre_delete_range_cb), equation);
1919 g_signal_connect_after(equation, "insert-text", G_CALLBACK(insert_text_cb), equation);
1920 g_signal_connect_after(equation, "delete-range", G_CALLBACK(delete_range_cb), equation);
1922 digits = g_strsplit(digit_values, ",", -1);
1923 for (i = 0; i < 16; i++) {
1924 if (use_default_digits || digits[i] == NULL) {
1925 use_default_digits = TRUE;
1926 equation->priv->digits[i] = g_utf8_get_char(default_digits[i]);
1928 else
1929 equation->priv->digits[i] = g_utf8_get_char(digits[i]);
1931 g_strfreev(digits);
1933 equation->priv->variables = math_variables_new();
1935 equation->priv->state.status = g_strdup("");
1936 equation->priv->word_size = 32;
1937 equation->priv->angle_units = MP_DEGREES;
1938 // FIXME: Pick based on locale
1939 equation->priv->source_currency = g_strdup("");
1940 equation->priv->target_currency = g_strdup("");
1941 equation->priv->source_units = g_strdup("");
1942 equation->priv->target_units = g_strdup("");
1943 equation->priv->serializer = mp_serializer_new(MP_DISPLAY_FORMAT_AUTOMATIC, 10, 9);
1944 equation->priv->queue = g_async_queue_new();
1946 mp_set_from_integer(0, &equation->priv->state.ans);