Add g_return_if_fail to all public methods
[gcalctool.git] / src / math-equation.c
blobf0c0975a570cef579bc5737d1d9eb036c4486076
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_accuracy(equation->priv->serializer) == accuracy)
517 return;
518 mp_serializer_set_accuracy(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_accuracy(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 text = math_equation_get_equation(equation);
875 result = !mp_serializer_from_string(equation->priv->serializer, text, z);
876 g_free(text);
878 return result;
882 MpSerializer *
883 math_equation_get_serializer(MathEquation *equation)
885 g_return_val_if_fail(equation != NULL, NULL);
886 return equation->priv->serializer;
890 void
891 math_equation_set_number_mode(MathEquation *equation, NumberMode mode)
893 g_return_if_fail(equation != NULL);
895 if (equation->priv->number_mode == mode)
896 return;
898 equation->priv->can_super_minus = mode == SUPERSCRIPT;
900 equation->priv->number_mode = mode;
901 g_object_notify(G_OBJECT(equation), "number-mode");
905 NumberMode
906 math_equation_get_number_mode(MathEquation *equation)
908 g_return_val_if_fail(equation != NULL, NORMAL);
909 return equation->priv->number_mode;
913 gboolean
914 math_equation_in_solve(MathEquation *equation)
916 g_return_val_if_fail(equation != NULL, FALSE);
917 return equation->priv->in_solve;
921 const MPNumber *
922 math_equation_get_answer(MathEquation *equation)
924 g_return_val_if_fail(equation != NULL, FALSE);
925 return &equation->priv->state.ans;
929 void
930 math_equation_store(MathEquation *equation, const gchar *name)
932 MPNumber t;
934 g_return_if_fail(equation != NULL);
935 g_return_if_fail(name != NULL);
937 if (!math_equation_get_number(equation, &t))
938 math_equation_set_status(equation, _("No sane value to store"));
939 else
940 math_variables_set(equation->priv->variables, name, &t);
944 void
945 math_equation_recall(MathEquation *equation, const gchar *name)
947 g_return_if_fail(equation != NULL);
948 g_return_if_fail(name != NULL);
949 math_equation_insert(equation, name);
953 void
954 math_equation_set(MathEquation *equation, const gchar *text)
956 g_return_if_fail(equation != NULL);
957 g_return_if_fail(text != NULL);
958 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), text, -1);
959 clear_ans(equation, FALSE);
963 void
964 math_equation_set_number(MathEquation *equation, const MPNumber *x)
966 char *text;
967 GtkTextIter start, end;
969 g_return_if_fail(equation != NULL);
970 g_return_if_fail(x != NULL);
972 /* Show the number in the user chosen format */
973 text = mp_serializer_to_string(equation->priv->serializer, x);
974 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), text, -1);
975 mp_set_from_mp(x, &equation->priv->state.ans);
977 /* Mark this text as the answer variable */
978 gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(equation), &start, &end);
979 clear_ans(equation, FALSE);
980 equation->priv->ans_start = gtk_text_buffer_create_mark(GTK_TEXT_BUFFER(equation), NULL, &start, FALSE);
981 equation->priv->ans_end = gtk_text_buffer_create_mark(GTK_TEXT_BUFFER(equation), NULL, &end, TRUE);
982 gtk_text_buffer_apply_tag(GTK_TEXT_BUFFER(equation), equation->priv->ans_tag, &start, &end);
983 g_free(text);
987 void
988 math_equation_insert(MathEquation *equation, const gchar *text)
990 g_return_if_fail(equation != NULL);
991 g_return_if_fail(text != NULL);
993 /* Replace ** with ^ (not on all keyboards) */
994 if (!gtk_text_buffer_get_has_selection(GTK_TEXT_BUFFER(equation)) &&
995 strcmp(text, "×") == 0 && equation->priv->state.entered_multiply) {
996 GtkTextIter iter;
998 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, gtk_text_buffer_get_insert(GTK_TEXT_BUFFER(equation)));
999 gtk_text_buffer_backspace(GTK_TEXT_BUFFER(equation), &iter, TRUE, TRUE);
1000 gtk_text_buffer_insert_at_cursor(GTK_TEXT_BUFFER(equation), "^", -1);
1001 return;
1004 /* Start new equation when entering digits after existing result */
1005 if(math_equation_is_result(equation) && g_unichar_isdigit(g_utf8_get_char(text)))
1006 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), "", -1);
1008 /* Can't enter superscript minus after entering digits */
1009 if (strstr("⁰¹²³⁴⁵⁶⁷⁸⁹", text) != NULL || strcmp("⁻", text) == 0)
1010 equation->priv->can_super_minus = FALSE;
1012 /* Disable super/subscript mode when finished entering */
1013 if (strstr("⁻⁰¹²³⁴⁵⁶⁷⁸⁹₀₁₂₃₄₅₆₇₈₉", text) == NULL)
1014 math_equation_set_number_mode(equation, NORMAL);
1016 gtk_text_buffer_delete_selection(GTK_TEXT_BUFFER(equation), FALSE, FALSE);
1017 gtk_text_buffer_insert_at_cursor(GTK_TEXT_BUFFER(equation), text, -1);
1021 void
1022 math_equation_insert_digit(MathEquation *equation, guint digit)
1024 static const char *subscript_digits[] = {"₀", "₁", "₂", "₃", "₄", "₅", "₆", "₇", "₈", "₉", NULL};
1025 static const char *superscript_digits[] = {"⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹", NULL};
1027 g_return_if_fail(equation != NULL);
1028 g_return_if_fail(digit < 16);
1030 if (equation->priv->number_mode == NORMAL || digit >= 10) {
1031 gchar buffer[7];
1032 gint len;
1033 len = g_unichar_to_utf8(math_equation_get_digit_text(equation, digit), buffer);
1034 buffer[len] = '\0';
1035 math_equation_insert(equation, buffer);
1037 else if (equation->priv->number_mode == SUPERSCRIPT)
1038 math_equation_insert(equation, superscript_digits[digit]);
1039 else if (equation->priv->number_mode == SUBSCRIPT)
1040 math_equation_insert(equation, subscript_digits[digit]);
1044 void
1045 math_equation_insert_numeric_point(MathEquation *equation)
1047 gchar buffer[7];
1048 gint len;
1050 g_return_if_fail(equation != NULL);
1052 len = g_unichar_to_utf8(mp_serializer_get_radix(equation->priv->serializer), buffer);
1053 buffer[len] = '\0';
1054 math_equation_insert(equation, buffer);
1058 void
1059 math_equation_insert_number(MathEquation *equation, const MPNumber *x)
1061 char *text;
1063 g_return_if_fail(equation != NULL);
1064 g_return_if_fail(x != NULL);
1066 text = mp_serializer_to_string(equation->priv->serializer, x);
1067 math_equation_insert(equation, text);
1068 g_free(text);
1072 void
1073 math_equation_insert_exponent(MathEquation *equation)
1075 g_return_if_fail(equation != NULL);
1076 math_equation_insert(equation, "×10");
1077 math_equation_set_number_mode(equation, SUPERSCRIPT);
1081 void
1082 math_equation_insert_subtract(MathEquation *equation)
1084 g_return_if_fail(equation != NULL);
1085 if (equation->priv->number_mode == SUPERSCRIPT && equation->priv->can_super_minus) {
1086 math_equation_insert(equation, "⁻");
1087 equation->priv->can_super_minus = FALSE;
1089 else {
1090 math_equation_insert(equation, "−");
1091 math_equation_set_number_mode(equation, NORMAL);
1096 static int
1097 variable_is_defined(const char *name, void *data)
1099 MathEquation *equation = data;
1100 char *c, *lower_name;
1102 lower_name = strdup(name);
1103 for (c = lower_name; *c; c++)
1104 *c = tolower(*c);
1106 if (strcmp(lower_name, "rand") == 0 ||
1107 strcmp(lower_name, "ans") == 0) {
1108 g_free(lower_name);
1109 return 1;
1111 g_free(lower_name);
1113 return math_variables_get(equation->priv->variables, name) != NULL;
1117 static int
1118 get_variable(const char *name, MPNumber *z, void *data)
1120 char *c, *lower_name;
1121 int result = 1;
1122 MathEquation *equation = data;
1123 MPNumber *t;
1125 lower_name = strdup(name);
1126 for (c = lower_name; *c; c++)
1127 *c = tolower(*c);
1129 if (strcmp(lower_name, "rand") == 0)
1130 mp_set_from_random(z);
1131 else if (strcmp(lower_name, "ans") == 0)
1132 mp_set_from_mp(&equation->priv->state.ans, z);
1133 else {
1134 t = math_variables_get(equation->priv->variables, name);
1135 if (t)
1136 mp_set_from_mp(t, z);
1137 else
1138 result = 0;
1141 free(lower_name);
1143 return result;
1147 static void
1148 set_variable(const char *name, const MPNumber *x, void *data)
1150 MathEquation *equation = data;
1151 /* FIXME: Don't allow writing to built-in variables, e.g. ans, rand, sin, ... */
1152 math_variables_set(equation->priv->variables, name, x);
1156 static int
1157 convert(const MPNumber *x, const char *x_units, const char *z_units, MPNumber *z, void *data)
1159 return unit_manager_convert_by_symbol(unit_manager_get_default(), x, x_units, z_units, z);
1163 static int
1164 parse(MathEquation *equation, const char *text, MPNumber *z, char **error_token)
1166 MPEquationOptions options;
1168 memset(&options, 0, sizeof(options));
1169 options.base = mp_serializer_get_base(equation->priv->serializer);
1170 options.wordlen = equation->priv->word_size;
1171 options.angle_units = equation->priv->angle_units;
1172 options.variable_is_defined = variable_is_defined;
1173 options.get_variable = get_variable;
1174 options.set_variable = set_variable;
1175 options.convert = convert;
1176 options.callback_data = equation;
1178 return mp_equation_parse(text, &options, z, error_token);
1183 * Executed in separate thread. It is thus not a good idea to write to anything
1184 * in MathEquation but the async queue from here.
1186 static gpointer
1187 math_equation_solve_real(gpointer data)
1189 MathEquation *equation = MATH_EQUATION(data);
1190 SolveData *solvedata = g_slice_new0(SolveData);
1192 gint n_brackets = 0, result;
1193 gchar *c, *text, *error_token;
1194 GString *equation_text;
1195 MPNumber z;
1197 text = math_equation_get_equation(equation);
1198 equation_text = g_string_new(text);
1199 g_free(text);
1200 /* Count the number of brackets and automatically add missing closing brackets */
1201 for (c = equation_text->str; *c; c++) {
1202 if (*c == '(')
1203 n_brackets++;
1204 else if (*c == ')')
1205 n_brackets--;
1207 while (n_brackets > 0) {
1208 g_string_append_c(equation_text, ')');
1209 n_brackets--;
1213 result = parse(equation, equation_text->str, &z, &error_token);
1214 g_string_free(equation_text, TRUE);
1216 switch (result) {
1217 case PARSER_ERR_NONE:
1218 solvedata->number_result = g_slice_new(MPNumber);
1219 mp_set_from_mp(&z, solvedata->number_result);
1220 break;
1222 case PARSER_ERR_OVERFLOW:
1223 solvedata->error = g_strdup(/* Error displayed to user when they perform a bitwise operation on numbers greater than the current word */
1224 _("Overflow. Try a bigger word size"));
1225 break;
1227 case PARSER_ERR_UNKNOWN_VARIABLE:
1228 solvedata->error = g_strdup_printf(/* Error displayed to user when they an unknown variable is entered */
1229 _("Unknown variable '%s'"), error_token);
1230 break;
1232 case PARSER_ERR_UNKNOWN_FUNCTION:
1233 solvedata->error = g_strdup_printf(/* Error displayed to user when an unknown function is entered */
1234 _("Function '%s' is not defined"), error_token);
1235 break;
1237 case PARSER_ERR_UNKNOWN_CONVERSION:
1238 solvedata->error = g_strdup(/* Error displayed to user when an conversion with unknown units is attempted */
1239 _("Unknown conversion"));
1240 break;
1242 case PARSER_ERR_MP:
1243 solvedata->error = g_strdup(mp_get_error());
1244 break;
1246 default:
1247 solvedata->error = g_strdup(/* Error displayed to user when they enter an invalid calculation */
1248 _("Malformed expression"));
1249 break;
1251 g_async_queue_push(equation->priv->queue, solvedata);
1253 return NULL;
1257 static gboolean
1258 math_equation_show_in_progress(gpointer data)
1260 MathEquation *equation = MATH_EQUATION(data);
1261 if (equation->priv->in_solve)
1262 math_equation_set_status(equation, _("Calculating"));
1263 return false;
1267 static gboolean
1268 math_equation_look_for_answer(gpointer data)
1270 MathEquation *equation = MATH_EQUATION(data);
1271 SolveData *result = g_async_queue_try_pop(equation->priv->queue);
1273 if (result == NULL)
1274 return true;
1276 equation->priv->in_solve = false;
1278 if (!result->error)
1279 math_equation_set_status(equation, "");
1281 if (result->error != NULL) {
1282 math_equation_set_status(equation, result->error);
1283 g_free(result->error);
1285 else if (result->number_result != NULL) {
1286 math_equation_set_number(equation, result->number_result);
1287 g_slice_free(MPNumber, result->number_result);
1289 else if (result->text_result != NULL) {
1290 math_equation_set(equation, result->text_result);
1291 g_free(result->text_result);
1293 g_slice_free(SolveData, result);
1295 return false;
1299 void
1300 math_equation_solve(MathEquation *equation)
1302 GError *error = NULL;
1304 g_return_if_fail(equation != NULL);
1306 // FIXME: should replace calculation or give error message
1307 if (equation->priv->in_solve)
1308 return;
1310 if (math_equation_is_empty(equation))
1311 return;
1313 /* If showing a result return to the equation that caused it */
1314 // FIXME: Result may not be here due to solve (i.e. the user may have entered "ans")
1315 if (math_equation_is_result(equation)) {
1316 math_equation_undo(equation);
1317 return;
1320 equation->priv->in_solve = true;
1322 math_equation_set_number_mode(equation, NORMAL);
1324 g_thread_create(math_equation_solve_real, equation, false, &error);
1326 if (error)
1327 g_warning("Error spawning thread for calculations: %s\n", error->message);
1329 g_timeout_add(50, math_equation_look_for_answer, equation);
1330 g_timeout_add(100, math_equation_show_in_progress, equation);
1334 static gpointer
1335 math_equation_factorize_real(gpointer data)
1337 GString *text;
1338 GList *factors, *factor;
1339 MPNumber x;
1340 MathEquation *equation = MATH_EQUATION(data);
1341 SolveData *result = g_slice_new0(SolveData);
1343 math_equation_get_number(equation, &x);
1344 factors = mp_factorize(&x);
1346 text = g_string_new("");
1348 for (factor = factors; factor; factor = factor->next) {
1349 gchar *temp;
1350 MPNumber *n;
1352 n = factor->data;
1353 temp = mp_serializer_to_string(equation->priv->serializer, n);
1354 g_string_append(text, temp);
1355 if (factor->next)
1356 g_string_append(text, "×");
1357 g_slice_free(MPNumber, n);
1358 g_free(temp);
1360 g_list_free(factors);
1362 result->text_result = g_strndup(text->str, text->len);
1363 g_async_queue_push(equation->priv->queue, result);
1364 g_string_free(text, TRUE);
1366 return NULL;
1370 void
1371 math_equation_factorize(MathEquation *equation)
1373 MPNumber x;
1374 GError *error = NULL;
1376 g_return_if_fail(equation != NULL);
1378 // FIXME: should replace calculation or give error message
1379 if (equation->priv->in_solve)
1380 return;
1382 if (!math_equation_get_number(equation, &x) || !mp_is_integer(&x)) {
1383 /* Error displayed when trying to factorize a non-integer value */
1384 math_equation_set_status(equation, _("Need an integer to factorize"));
1385 return;
1388 equation->priv->in_solve = true;
1390 g_thread_create(math_equation_factorize_real, equation, false, &error);
1392 if (error)
1393 g_warning("Error spawning thread for calculations: %s\n", error->message);
1395 g_timeout_add(50, math_equation_look_for_answer, equation);
1396 g_timeout_add(100, math_equation_show_in_progress, equation);
1400 void
1401 math_equation_delete(MathEquation *equation)
1403 gint cursor;
1404 GtkTextIter start, end;
1406 g_return_if_fail(equation != NULL);
1408 g_object_get(G_OBJECT(equation), "cursor-position", &cursor, NULL);
1409 if (cursor >= gtk_text_buffer_get_char_count(GTK_TEXT_BUFFER(equation)))
1410 return;
1412 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &start, cursor);
1413 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &end, cursor+1);
1414 gtk_text_buffer_delete(GTK_TEXT_BUFFER(equation), &start, &end);
1418 void
1419 math_equation_backspace(MathEquation *equation)
1421 g_return_if_fail(equation != NULL);
1423 /* Can't delete empty display */
1424 if (math_equation_is_empty(equation))
1425 return;
1427 if (gtk_text_buffer_get_has_selection(GTK_TEXT_BUFFER(equation)))
1428 gtk_text_buffer_delete_selection(GTK_TEXT_BUFFER(equation), FALSE, FALSE);
1429 else {
1430 GtkTextIter iter;
1431 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, gtk_text_buffer_get_insert(GTK_TEXT_BUFFER(equation)));
1432 gtk_text_buffer_backspace(GTK_TEXT_BUFFER(equation), &iter, TRUE, TRUE);
1437 void
1438 math_equation_clear(MathEquation *equation)
1440 g_return_if_fail(equation != NULL);
1442 math_equation_set_number_mode(equation, NORMAL);
1443 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), "", -1);
1444 clear_ans(equation, FALSE);
1448 void
1449 math_equation_shift(MathEquation *equation, gint count)
1451 MPNumber z;
1453 g_return_if_fail(equation != NULL);
1455 if (!math_equation_get_number(equation, &z)) {
1456 math_equation_set_status(equation,
1457 /* This message is displayed in the status bar when a bit
1458 shift operation is performed and the display does not contain a number */
1459 _("No sane value to bitwise shift"));
1460 return;
1463 mp_shift(&z, count, &z);
1464 math_equation_set_number(equation, &z);
1468 void
1469 math_equation_toggle_bit(MathEquation *equation, guint bit)
1471 MPNumber x;
1472 guint64 bits;
1473 gboolean result;
1475 g_return_if_fail(equation != NULL);
1477 result = math_equation_get_number(equation, &x);
1478 if (result) {
1479 MPNumber max;
1480 mp_set_from_unsigned_integer(G_MAXUINT64, &max);
1481 if (mp_is_negative(&x) || mp_is_greater_than(&x, &max))
1482 result = FALSE;
1483 else
1484 bits = mp_cast_to_unsigned_int(&x);
1487 if (!result) {
1488 math_equation_set_status(equation,
1489 /* Message displayed when cannot toggle bit in display*/
1490 _("Displayed value not an integer"));
1491 return;
1494 bits ^= (1LL << (63 - bit));
1496 mp_set_from_unsigned_integer(bits, &x);
1498 // FIXME: Only do this if in ans format, otherwise set text in same format as previous number
1499 math_equation_set_number(equation, &x);
1503 static void
1504 math_equation_set_property(GObject *object,
1505 guint prop_id,
1506 const GValue *value,
1507 GParamSpec *pspec)
1509 MathEquation *self;
1511 self = MATH_EQUATION(object);
1513 switch (prop_id) {
1514 case PROP_STATUS:
1515 math_equation_set_status(self, g_value_get_string(value));
1516 break;
1517 case PROP_DISPLAY:
1518 math_equation_set(self, g_value_get_string(value));
1519 break;
1520 case PROP_NUMBER_MODE:
1521 math_equation_set_number_mode(self, g_value_get_int(value));
1522 break;
1523 case PROP_ACCURACY:
1524 math_equation_set_accuracy(self, g_value_get_int(value));
1525 break;
1526 case PROP_SHOW_THOUSANDS_SEPARATORS:
1527 math_equation_set_show_thousands_separators(self, g_value_get_boolean(value));
1528 break;
1529 case PROP_SHOW_TRAILING_ZEROES:
1530 math_equation_set_show_trailing_zeroes(self, g_value_get_boolean(value));
1531 break;
1532 case PROP_NUMBER_FORMAT:
1533 math_equation_set_number_format(self, g_value_get_int(value));
1534 break;
1535 case PROP_BASE:
1536 math_equation_set_base(self, g_value_get_int(value));
1537 break;
1538 case PROP_WORD_SIZE:
1539 math_equation_set_word_size(self, g_value_get_int(value));
1540 break;
1541 case PROP_ANGLE_UNITS:
1542 math_equation_set_angle_units(self, g_value_get_int(value));
1543 break;
1544 case PROP_SOURCE_CURRENCY:
1545 math_equation_set_source_currency(self, g_value_get_string(value));
1546 break;
1547 case PROP_TARGET_CURRENCY:
1548 math_equation_set_target_currency(self, g_value_get_string(value));
1549 break;
1550 case PROP_SOURCE_UNITS:
1551 math_equation_set_source_units(self, g_value_get_string(value));
1552 break;
1553 case PROP_TARGET_UNITS:
1554 math_equation_set_target_units(self, g_value_get_string(value));
1555 break;
1556 default:
1557 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
1558 break;
1563 static void
1564 math_equation_get_property(GObject *object,
1565 guint prop_id,
1566 GValue *value,
1567 GParamSpec *pspec)
1569 MathEquation *self;
1570 gchar *text;
1572 self = MATH_EQUATION(object);
1574 switch (prop_id) {
1575 case PROP_STATUS:
1576 g_value_set_string(value, self->priv->state.status);
1577 break;
1578 case PROP_DISPLAY:
1579 text = math_equation_get_display(self);
1580 g_value_set_string(value, text);
1581 g_free(text);
1582 break;
1583 case PROP_EQUATION:
1584 text = math_equation_get_equation(self);
1585 g_value_set_string(value, text);
1586 g_free(text);
1587 break;
1588 case PROP_NUMBER_MODE:
1589 g_value_set_enum(value, self->priv->number_mode);
1590 break;
1591 case PROP_ACCURACY:
1592 g_value_set_int(value, mp_serializer_get_accuracy(self->priv->serializer));
1593 break;
1594 case PROP_SHOW_THOUSANDS_SEPARATORS:
1595 g_value_set_boolean(value, mp_serializer_get_show_thousands_separators(self->priv->serializer));
1596 break;
1597 case PROP_SHOW_TRAILING_ZEROES:
1598 g_value_set_boolean(value, mp_serializer_get_show_trailing_zeroes(self->priv->serializer));
1599 break;
1600 case PROP_NUMBER_FORMAT:
1601 g_value_set_enum(value, mp_serializer_get_number_format(self->priv->serializer));
1602 break;
1603 case PROP_BASE:
1604 g_value_set_int(value, math_equation_get_base(self));
1605 break;
1606 case PROP_WORD_SIZE:
1607 g_value_set_int(value, self->priv->word_size);
1608 break;
1609 case PROP_ANGLE_UNITS:
1610 g_value_set_enum(value, self->priv->angle_units);
1611 break;
1612 case PROP_SOURCE_CURRENCY:
1613 g_value_set_string(value, self->priv->source_currency);
1614 break;
1615 case PROP_TARGET_CURRENCY:
1616 g_value_set_string(value, self->priv->target_currency);
1617 break;
1618 case PROP_SOURCE_UNITS:
1619 g_value_set_string(value, self->priv->source_units);
1620 break;
1621 case PROP_TARGET_UNITS:
1622 g_value_set_string(value, self->priv->target_units);
1623 break;
1624 case PROP_SERIALIZER:
1625 g_value_set_object(value, self->priv->serializer);
1626 break;
1627 default:
1628 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
1629 break;
1634 static void
1635 math_equation_constructed(GObject *object)
1637 GtkTextBuffer *parent_class;
1638 parent_class = g_type_class_peek_parent(MATH_EQUATION_GET_CLASS(object));
1639 if (G_OBJECT_CLASS(parent_class)->constructed)
1640 G_OBJECT_CLASS(parent_class)->constructed(object);
1642 MATH_EQUATION(object)->priv->ans_tag = gtk_text_buffer_create_tag(GTK_TEXT_BUFFER(object), NULL, "weight", PANGO_WEIGHT_BOLD, NULL);
1646 static void
1647 math_equation_class_init(MathEquationClass *klass)
1649 static GEnumValue number_mode_values[] =
1651 {NORMAL, "normal", "normal"},
1652 {SUPERSCRIPT, "superscript", "superscript"},
1653 {SUBSCRIPT, "subscript", "subscript"},
1654 {0, NULL, NULL}
1656 static GEnumValue angle_unit_values[] =
1658 {MP_RADIANS, "radians", "radians"},
1659 {MP_DEGREES, "degrees", "degrees"},
1660 {MP_GRADIANS, "gradians", "gradians"},
1661 {0, NULL, NULL}
1663 GObjectClass *object_class = G_OBJECT_CLASS(klass);
1665 object_class->get_property = math_equation_get_property;
1666 object_class->set_property = math_equation_set_property;
1667 object_class->constructed = math_equation_constructed;
1669 g_type_class_add_private(klass, sizeof(MathEquationPrivate));
1671 number_mode_type = g_enum_register_static("NumberMode", number_mode_values);
1672 number_format_type = math_mp_display_format_get_type();
1673 angle_unit_type = g_enum_register_static("AngleUnit", angle_unit_values);
1675 g_object_class_install_property(object_class,
1676 PROP_STATUS,
1677 g_param_spec_string("status",
1678 "status",
1679 "Equation status",
1681 G_PARAM_READWRITE));
1682 g_object_class_install_property(object_class,
1683 PROP_DISPLAY,
1684 g_param_spec_string("display",
1685 "display",
1686 "Displayed equation text",
1688 G_PARAM_READWRITE));
1689 g_object_class_install_property(object_class,
1690 PROP_EQUATION,
1691 g_param_spec_string("equation",
1692 "equation",
1693 "Equation text",
1695 G_PARAM_READABLE));
1696 g_object_class_install_property(object_class,
1697 PROP_NUMBER_MODE,
1698 g_param_spec_enum("number-mode",
1699 "number-mode",
1700 "Input number mode",
1701 number_mode_type,
1702 NORMAL,
1703 G_PARAM_READWRITE));
1704 g_object_class_install_property(object_class,
1705 PROP_ACCURACY,
1706 g_param_spec_int("accuracy",
1707 "accuracy",
1708 "Display accuracy",
1709 0, 20, 9,
1710 G_PARAM_READWRITE));
1711 g_object_class_install_property(object_class,
1712 PROP_SHOW_THOUSANDS_SEPARATORS,
1713 g_param_spec_boolean("show-thousands-separators",
1714 "show-thousands-separators",
1715 "Show thousands separators",
1716 TRUE,
1717 G_PARAM_READWRITE));
1718 g_object_class_install_property(object_class,
1719 PROP_SHOW_TRAILING_ZEROES,
1720 g_param_spec_boolean("show-trailing-zeroes",
1721 "show-trailing-zeroes",
1722 "Show trailing zeroes",
1723 FALSE,
1724 G_PARAM_READWRITE));
1725 g_object_class_install_property(object_class,
1726 PROP_NUMBER_FORMAT,
1727 g_param_spec_enum("number-format",
1728 "number-format",
1729 "Display format",
1730 number_format_type,
1731 MP_DISPLAY_FORMAT_FIXED,
1732 G_PARAM_READWRITE));
1733 g_object_class_install_property(object_class,
1734 PROP_BASE,
1735 g_param_spec_int("base",
1736 "base",
1737 "Default number base (derived from number-format)",
1738 2, 16, 10,
1739 G_PARAM_READWRITE));
1740 g_object_class_install_property(object_class,
1741 PROP_WORD_SIZE,
1742 g_param_spec_int("word-size",
1743 "word-size",
1744 "Word size in bits",
1745 8, 64, 64,
1746 G_PARAM_READWRITE));
1747 g_object_class_install_property(object_class,
1748 PROP_ANGLE_UNITS,
1749 g_param_spec_enum("angle-units",
1750 "angle-units",
1751 "Angle units",
1752 angle_unit_type,
1753 MP_DEGREES,
1754 G_PARAM_READWRITE));
1755 g_object_class_install_property(object_class,
1756 PROP_SOURCE_CURRENCY,
1757 g_param_spec_string("source-currency",
1758 "source-currency",
1759 "Source Currency",
1761 G_PARAM_READWRITE));
1762 g_object_class_install_property(object_class,
1763 PROP_TARGET_CURRENCY,
1764 g_param_spec_string("target-currency",
1765 "target-currency",
1766 "target Currency",
1768 G_PARAM_READWRITE));
1769 g_object_class_install_property(object_class,
1770 PROP_SOURCE_UNITS,
1771 g_param_spec_string("source-units",
1772 "source-units",
1773 "Source Units",
1775 G_PARAM_READWRITE));
1776 g_object_class_install_property(object_class,
1777 PROP_TARGET_UNITS,
1778 g_param_spec_string("target-units",
1779 "target-units",
1780 "target Units",
1782 G_PARAM_READWRITE));
1783 g_object_class_install_property(object_class,
1784 PROP_SERIALIZER,
1785 g_param_spec_object("serializer",
1786 "serializer",
1787 "Serializer",
1788 MP_TYPE_SERIALIZER,
1789 G_PARAM_READABLE));
1793 static void
1794 pre_insert_text_cb(MathEquation *equation,
1795 GtkTextIter *location,
1796 gchar *text,
1797 gint len,
1798 gpointer user_data)
1800 gunichar c;
1802 if (equation->priv->in_reformat)
1803 return;
1805 /* If following a delete then have already pushed undo stack (GtkTextBuffer
1806 doesn't indicate replace operations so we have to infer them) */
1807 if (!equation->priv->in_delete)
1808 math_equation_push_undo_stack(equation);
1810 /* Clear result on next digit entered if cursor at end of line */
1811 // FIXME Cursor
1812 c = g_utf8_get_char(text);
1813 if ((g_unichar_isdigit(c) || c == mp_serializer_get_radix(equation->priv->serializer)) &&
1814 math_equation_is_result(equation)) {
1815 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), "", -1);
1816 clear_ans(equation, FALSE);
1817 gtk_text_buffer_get_end_iter(GTK_TEXT_BUFFER(equation), location);
1820 if (equation->priv->ans_start) {
1821 gint ans_start, ans_end;
1822 gint offset;
1824 offset = gtk_text_iter_get_offset(location);
1825 get_ans_offsets(equation, &ans_start, &ans_end);
1827 /* Inserted inside ans */
1828 if (offset > ans_start && offset < ans_end)
1829 clear_ans(equation, TRUE);
1834 static gboolean
1835 on_delete(MathEquation *equation)
1837 equation->priv->in_delete = FALSE;
1838 return FALSE;
1842 static void
1843 pre_delete_range_cb(MathEquation *equation,
1844 GtkTextIter *start,
1845 GtkTextIter *end,
1846 gpointer user_data)
1848 if (equation->priv->in_reformat)
1849 return;
1851 math_equation_push_undo_stack(equation);
1853 equation->priv->in_delete = TRUE;
1854 g_idle_add((GSourceFunc)on_delete, equation);
1856 if (equation->priv->ans_start) {
1857 gint ans_start, ans_end;
1858 gint start_offset, end_offset;
1860 start_offset = gtk_text_iter_get_offset(start);
1861 end_offset = gtk_text_iter_get_offset(end);
1862 get_ans_offsets(equation, &ans_start, &ans_end);
1864 /* Deleted part of ans */
1865 if (start_offset < ans_end && end_offset > ans_start)
1866 clear_ans(equation, TRUE);
1871 static void
1872 insert_text_cb(MathEquation *equation,
1873 GtkTextIter *location,
1874 gchar *text,
1875 gint len,
1876 gpointer user_data)
1878 if (equation->priv->in_reformat)
1879 return;
1881 equation->priv->state.entered_multiply = strcmp(text, "×") == 0;
1883 /* Update thousands separators */
1884 reformat_separators(equation);
1886 g_object_notify(G_OBJECT(equation), "display");
1890 static void
1891 delete_range_cb(MathEquation *equation,
1892 GtkTextIter *start,
1893 GtkTextIter *end,
1894 gpointer user_data)
1896 if (equation->priv->in_reformat)
1897 return;
1899 equation->priv->state.entered_multiply = FALSE;
1901 /* Update thousands separators */
1902 reformat_separators(equation);
1904 // FIXME: A replace will emit this both for delete-range and insert-text, can it be avoided?
1905 g_object_notify(G_OBJECT(equation), "display");
1909 static void
1910 math_equation_init(MathEquation *equation)
1912 /* Digits localized for the given language */
1913 const char *digit_values = _("0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F");
1914 const char *default_digits[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"};
1915 gchar **digits;
1916 /* Default to using untranslated digits, this is because it doesn't make sense in most languages and we need to make this optional.
1917 * See https://bugzilla.gnome.org/show_bug.cgi?id=632661 */
1918 gboolean use_default_digits = TRUE;
1919 int i;
1921 equation->priv = G_TYPE_INSTANCE_GET_PRIVATE(equation, math_equation_get_type(), MathEquationPrivate);
1923 g_signal_connect(equation, "insert-text", G_CALLBACK(pre_insert_text_cb), equation);
1924 g_signal_connect(equation, "delete-range", G_CALLBACK(pre_delete_range_cb), equation);
1925 g_signal_connect_after(equation, "insert-text", G_CALLBACK(insert_text_cb), equation);
1926 g_signal_connect_after(equation, "delete-range", G_CALLBACK(delete_range_cb), equation);
1928 digits = g_strsplit(digit_values, ",", -1);
1929 for (i = 0; i < 16; i++) {
1930 if (use_default_digits || digits[i] == NULL) {
1931 use_default_digits = TRUE;
1932 equation->priv->digits[i] = g_utf8_get_char(default_digits[i]);
1934 else
1935 equation->priv->digits[i] = g_utf8_get_char(digits[i]);
1937 g_strfreev(digits);
1939 equation->priv->variables = math_variables_new();
1941 equation->priv->state.status = g_strdup("");
1942 equation->priv->word_size = 32;
1943 equation->priv->angle_units = MP_DEGREES;
1944 // FIXME: Pick based on locale
1945 equation->priv->source_currency = g_strdup("");
1946 equation->priv->target_currency = g_strdup("");
1947 equation->priv->source_units = g_strdup("");
1948 equation->priv->target_units = g_strdup("");
1949 equation->priv->serializer = mp_serializer_new(MP_DISPLAY_FORMAT_AUTOMATIC, 10, 9);
1950 equation->priv->queue = g_async_queue_new();
1952 mp_set_from_integer(0, &equation->priv->state.ans);