1 /*
2 * Copyright (C) 1987-2008 Sun Microsystems, Inc. All Rights Reserved.
3 * Copyright (C) 2008-2011 Robert Ancell
4 *
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.
10 */
11
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>
20
21 #include "math-equation.h"
22
23 #include "mp.h"
24 #include "mp-equation.h"
25 #include "mp-serializer.h"
26 #include "mp-enums.h"
27 #include "unit-manager.h"
28 #include "utility.h"
29
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
48 };
49
50 static GType number_mode_type, number_format_type, angle_unit_type;
51
52 /* Expression mode state */
53 typedef struct {
54 MPNumber ans; /* Previously calculated answer */
55 gchar *expression; /* Expression entered by user */
56 gint ans_start, ans_end; /* Start and end characters for ans variable in expression */
57 gint cursor; /* ??? */
58 NumberMode number_mode; /* ??? */
59 gboolean can_super_minus; /* TRUE if entering minus can generate a superscript minus */
60 gboolean entered_multiply; /* Last insert was a multiply character */
61 gchar *status; /* Equation status */
62 } MathEquationState;
63
64 struct MathEquationPrivate
65 {
66 GtkTextTag *ans_tag;
67
68 gint word_size; /* Word size in bits */
69 MPAngleUnit angle_units; /* Units for trigonometric functions */
70 char *source_currency;
71 char *target_currency;
72 char *source_units;
73 char *target_units;
74 NumberMode number_mode; /* ??? */
75 gboolean can_super_minus; /* TRUE if entering minus can generate a superscript minus */
76
77 gunichar digits[16]; /* Localized digits */
78
79 GtkTextMark *ans_start, *ans_end;
80
81 MathEquationState state; /* Equation state */
82 GList *undo_stack; /* History of expression mode states */
83 GList *redo_stack;
84 gboolean in_undo_operation;
85
86 gboolean in_reformat;
87
88 gboolean in_delete;
89
90 gboolean in_solve;
91
92 MathVariables *variables;
93 MpSerializer *serializer;
94
95 GAsyncQueue *queue;
96 };
97
98 typedef struct {
99 MPNumber *number_result;
100 gchar *text_result;
101 gchar *error;
102 } SolveData;
103
104 G_DEFINE_TYPE_WITH_PRIVATE (MathEquation, math_equation, GTK_TYPE_TEXT_BUFFER);
105
106
107 MathEquation *
math_equation_new()108 math_equation_new()
109 {
110 return g_object_new(math_equation_get_type(), NULL);
111 }
112
113
114 MathVariables *
math_equation_get_variables(MathEquation * equation)115 math_equation_get_variables(MathEquation *equation)
116 {
117 return equation->priv->variables;
118 }
119
120
121 static void
get_ans_offsets(MathEquation * equation,gint * start,gint * end)122 get_ans_offsets(MathEquation *equation, gint *start, gint *end)
123 {
124 GtkTextIter iter;
125
126 if (!equation->priv->ans_start) {
127 *start = *end = -1;
128 return;
129 }
130
131 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, equation->priv->ans_start);
132 *start = gtk_text_iter_get_offset(&iter);
133 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, equation->priv->ans_end);
134 *end = gtk_text_iter_get_offset(&iter);
135 }
136
137
138 static void
reformat_ans(MathEquation * equation)139 reformat_ans(MathEquation *equation)
140 {
141 if (!equation->priv->ans_start)
142 return;
143
144 gchar *orig_ans_text;
145 gchar *ans_text;
146 GtkTextIter ans_start, ans_end;
147
148 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &ans_start, equation->priv->ans_start);
149 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &ans_end, equation->priv->ans_end);
150 orig_ans_text = gtk_text_buffer_get_text(GTK_TEXT_BUFFER(equation), &ans_start, &ans_end, FALSE);
151 ans_text = mp_serializer_to_string(equation->priv->serializer, &equation->priv->state.ans);
152 if (strcmp(orig_ans_text, ans_text) != 0) {
153 gint start;
154
155 equation->priv->in_undo_operation = TRUE;
156 equation->priv->in_reformat = TRUE;
157
158 start = gtk_text_iter_get_offset(&ans_start);
159 gtk_text_buffer_delete(GTK_TEXT_BUFFER(equation), &ans_start, &ans_end);
160 gtk_text_buffer_insert_with_tags(GTK_TEXT_BUFFER(equation), &ans_end, ans_text, -1, equation->priv->ans_tag, NULL);
161
162 /* There seems to be a bug in the marks as they alternate being the correct and incorrect ways. Reset them */
163 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &ans_start, start);
164 gtk_text_buffer_move_mark(GTK_TEXT_BUFFER(equation), equation->priv->ans_start, &ans_start);
165 gtk_text_buffer_move_mark(GTK_TEXT_BUFFER(equation), equation->priv->ans_end, &ans_end);
166
167 equation->priv->in_reformat = FALSE;
168 equation->priv->in_undo_operation = FALSE;
169 }
170 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &ans_start, equation->priv->ans_start);
171 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &ans_end, equation->priv->ans_end);
172 g_free(orig_ans_text);
173 g_free(ans_text);
174 }
175
176
177 static gint
count_digits(MathEquation * equation,const gchar * text)178 count_digits(MathEquation *equation, const gchar *text)
179 {
180 const gchar *read_iter;
181 gint count = 0;
182
183 read_iter = text;
184 while (*read_iter != '\0') {
185 if (!g_unichar_isdigit(g_utf8_get_char(read_iter)))
186 return count;
187
188 read_iter = g_utf8_next_char(read_iter);
189
190 /* Allow a thousands separator between digits follow a digit */
191 if (g_utf8_get_char(read_iter) == mp_serializer_get_thousands_separator(equation->priv->serializer)) {
192 read_iter = g_utf8_next_char(read_iter);
193 if (!g_unichar_isdigit(g_utf8_get_char(read_iter)))
194 return count;
195 }
196
197 count++;
198 }
199
200 return count;
201 }
202
203
204 static void
reformat_separators(MathEquation * equation)205 reformat_separators(MathEquation *equation)
206 {
207 gchar *text, *read_iter;
208 gint ans_start, ans_end;
209 gint offset, digit_offset = 0;
210 gboolean in_number = FALSE, in_radix = FALSE, last_is_tsep = FALSE;
211
212 equation->priv->in_undo_operation = TRUE;
213 equation->priv->in_reformat = TRUE;
214
215 text = math_equation_get_display(equation);
216 get_ans_offsets(equation, &ans_start, &ans_end);
217 for (read_iter = text, offset = 0; *read_iter != '\0'; read_iter = g_utf8_next_char(read_iter), offset++) {
218 gunichar c;
219 gboolean expect_tsep;
220
221 /* See what digit this character is */
222 c = g_utf8_get_char(read_iter);
223
224 expect_tsep = math_equation_get_base(equation) == 10 &&
225 mp_serializer_get_show_thousands_separators(equation->priv->serializer) &&
226 in_number && !in_radix && !last_is_tsep &&
227 digit_offset > 0 && digit_offset % mp_serializer_get_thousands_separator_count(equation->priv->serializer) == 0;
228 last_is_tsep = FALSE;
229
230 /* Don't mess with ans */
231 if (offset >= ans_start && offset <= ans_end) {
232 in_number = in_radix = FALSE;
233 continue;
234 }
235 if (g_unichar_isdigit(c)) {
236 if (!in_number)
237 digit_offset = count_digits(equation, read_iter);
238 in_number = TRUE;
239
240 /* Expected a thousands separator between these digits - insert it */
241 if (expect_tsep) {
242 GtkTextIter iter;
243 gchar buffer[7];
244 gint len;
245
246 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &iter, offset);
247 len = g_unichar_to_utf8(mp_serializer_get_thousands_separator(equation->priv->serializer), buffer);
248 buffer[len] = '\0';
249 gtk_text_buffer_insert(GTK_TEXT_BUFFER(equation), &iter, buffer, -1);
250 offset++;
251 last_is_tsep = TRUE;
252 }
253
254 digit_offset--;
255 }
256 else if (c == mp_serializer_get_radix(equation->priv->serializer)) {
257 in_number = in_radix = TRUE;
258 }
259 else if (c == mp_serializer_get_thousands_separator(equation->priv->serializer)) {
260 /* Didn't expect thousands separator - delete it */
261 if (!expect_tsep && in_number) {
262 GtkTextIter start, end;
263 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &start, offset);
264 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &end, offset + 1);
265 gtk_text_buffer_delete(GTK_TEXT_BUFFER(equation), &start, &end);
266 offset--;
267 }
268 else
269 last_is_tsep = TRUE;
270 }
271 else {
272 in_number = in_radix = FALSE;
273 }
274 }
275
276 g_free(text);
277
278 equation->priv->in_reformat = FALSE;
279 equation->priv->in_undo_operation = FALSE;
280 }
281
282
283 static void
reformat_display(MathEquation * equation)284 reformat_display(MathEquation *equation)
285 {
286 /* Change ans */
287 reformat_ans(equation);
288
289 /* Add/remove thousands separators */
290 reformat_separators(equation);
291
292 g_signal_emit_by_name(equation, "display-changed");
293 }
294
295
296 static MathEquationState *
get_current_state(MathEquation * equation)297 get_current_state(MathEquation *equation)
298 {
299 MathEquationState *state;
300 gint ans_start = -1, ans_end = -1;
301
302 state = g_malloc0(sizeof(MathEquationState));
303 state->ans = mp_new();
304
305 if (equation->priv->ans_start)
306 {
307 GtkTextIter iter;
308 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, equation->priv->ans_start);
309 ans_start = gtk_text_iter_get_offset(&iter);
310 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, equation->priv->ans_end);
311 ans_end = gtk_text_iter_get_offset(&iter);
312 }
313
314 mp_set_from_mp(&equation->priv->state.ans, &state->ans);
315 state->expression = math_equation_get_display(equation);
316 state->ans_start = ans_start;
317 state->ans_end = ans_end;
318 g_object_get(G_OBJECT(equation), "cursor-position", &state->cursor, NULL);
319 state->number_mode = equation->priv->number_mode;
320 state->can_super_minus = equation->priv->can_super_minus;
321 state->entered_multiply = equation->priv->state.entered_multiply;
322 state->status = g_strdup(equation->priv->state.status);
323
324 return state;
325 }
326
327
328 static void
free_state(MathEquationState * state)329 free_state(MathEquationState *state)
330 {
331 g_free(state->expression);
332 g_free(state->status);
333 g_free(state);
334 }
335
336
337 static void
math_equation_push_undo_stack(MathEquation * equation)338 math_equation_push_undo_stack(MathEquation *equation)
339 {
340 GList *link;
341 MathEquationState *state;
342
343 if (equation->priv->in_undo_operation)
344 return;
345
346 math_equation_set_status(equation, "");
347
348 /* Can't redo anymore */
349 for (link = equation->priv->redo_stack; link; link = link->next) {
350 state = link->data;
351 free_state(state);
352 }
353 g_list_free(equation->priv->redo_stack);
354 equation->priv->redo_stack = NULL;
355
356 state = get_current_state(equation);
357 equation->priv->undo_stack = g_list_prepend(equation->priv->undo_stack, state);
358 }
359
360
361 static void
clear_ans(MathEquation * equation,gboolean remove_tag)362 clear_ans(MathEquation *equation, gboolean remove_tag)
363 {
364 if (!equation->priv->ans_start)
365 return;
366
367 if (remove_tag) {
368 GtkTextIter start, end;
369
370 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &start, equation->priv->ans_start);
371 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &end, equation->priv->ans_end);
372 gtk_text_buffer_remove_tag(GTK_TEXT_BUFFER(equation), equation->priv->ans_tag, &start, &end);
373 }
374
375 gtk_text_buffer_delete_mark(GTK_TEXT_BUFFER(equation), equation->priv->ans_start);
376 gtk_text_buffer_delete_mark(GTK_TEXT_BUFFER(equation), equation->priv->ans_end);
377 equation->priv->ans_start = NULL;
378 equation->priv->ans_end = NULL;
379 }
380
381
382 static void
apply_state(MathEquation * equation,MathEquationState * state)383 apply_state(MathEquation *equation, MathEquationState *state)
384 {
385 GtkTextIter cursor;
386
387 /* Disable undo detection */
388 equation->priv->in_undo_operation = TRUE;
389
390 mp_set_from_mp(&state->ans, &equation->priv->state.ans);
391
392 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), state->expression, -1);
393 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &cursor, state->cursor);
394 gtk_text_buffer_place_cursor(GTK_TEXT_BUFFER(equation), &cursor);
395 clear_ans(equation, FALSE);
396 if (state->ans_start >= 0) {
397 GtkTextIter start, end;
398
399 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &start, state->ans_start);
400 equation->priv->ans_start = gtk_text_buffer_create_mark(GTK_TEXT_BUFFER(equation), NULL, &start, FALSE);
401 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &end, state->ans_end);
402 equation->priv->ans_end = gtk_text_buffer_create_mark(GTK_TEXT_BUFFER(equation), NULL, &end, TRUE);
403 gtk_text_buffer_apply_tag(GTK_TEXT_BUFFER(equation), equation->priv->ans_tag, &start, &end);
404 }
405
406 math_equation_set_number_mode(equation, state->number_mode);
407 equation->priv->can_super_minus = state->can_super_minus;
408 equation->priv->state.entered_multiply = state->entered_multiply;
409 math_equation_set_status(equation, state->status);
410
411 equation->priv->in_undo_operation = FALSE;
412 }
413
414
415 void
math_equation_copy(MathEquation * equation)416 math_equation_copy(MathEquation *equation)
417 {
418 GtkTextIter start, end;
419 gchar *text;
420
421 g_return_if_fail(equation != NULL);
422
423 if (!gtk_text_buffer_get_selection_bounds(GTK_TEXT_BUFFER(equation), &start, &end))
424 gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(equation), &start, &end);
425
426 text = gtk_text_buffer_get_text(GTK_TEXT_BUFFER(equation), &start, &end, FALSE);
427 gtk_clipboard_set_text(gtk_clipboard_get(GDK_NONE), g_str_to_ascii (text, "C"), -1);
428 g_free(text);
429 }
430
431
432 static void
on_paste(GtkClipboard * clipboard,const gchar * text,gpointer data)433 on_paste(GtkClipboard *clipboard, const gchar *text, gpointer data)
434 {
435 MathEquation *equation = data;
436 if (text != NULL)
437 math_equation_insert(equation, text);
438 }
439
440
441 void
math_equation_paste(MathEquation * equation)442 math_equation_paste(MathEquation *equation)
443 {
444 g_return_if_fail(equation != NULL);
445 gtk_clipboard_request_text(gtk_clipboard_get(GDK_NONE), on_paste, equation);
446 }
447
448
449 void
math_equation_undo(MathEquation * equation)450 math_equation_undo(MathEquation *equation)
451 {
452 GList *link;
453 MathEquationState *state;
454
455 g_return_if_fail(equation != NULL);
456
457 if (!equation->priv->undo_stack) {
458 math_equation_set_status(equation,
459 /* Error shown when trying to undo with no undo history */
460 _("No undo history"));
461 return;
462 }
463
464 link = equation->priv->undo_stack;
465 equation->priv->undo_stack = g_list_remove_link(equation->priv->undo_stack, link);
466 state = link->data;
467 g_list_free(link);
468
469 equation->priv->redo_stack = g_list_prepend(equation->priv->redo_stack, get_current_state(equation));
470
471 apply_state(equation, state);
472 free_state(state);
473 }
474
475
476 void
math_equation_redo(MathEquation * equation)477 math_equation_redo(MathEquation *equation)
478 {
479 GList *link;
480 MathEquationState *state;
481
482 g_return_if_fail(equation != NULL);
483
484 if (!equation->priv->redo_stack) {
485 math_equation_set_status(equation,
486 /* Error shown when trying to redo with no redo history */
487 _("No redo history"));
488 return;
489 }
490
491 link = equation->priv->redo_stack;
492 equation->priv->redo_stack = g_list_remove_link(equation->priv->redo_stack, link);
493 state = link->data;
494 g_list_free(link);
495
496 equation->priv->undo_stack = g_list_prepend(equation->priv->undo_stack, get_current_state(equation));
497
498 apply_state(equation, state);
499 free_state(state);
500 }
501
502
503 gunichar
math_equation_get_digit_text(MathEquation * equation,guint digit)504 math_equation_get_digit_text(MathEquation *equation, guint digit)
505 {
506 g_return_val_if_fail(equation != NULL, '?');
507 g_return_val_if_fail(digit < 16, '?');
508
509 return equation->priv->digits[digit];
510 }
511
512
513 void
math_equation_set_accuracy(MathEquation * equation,gint accuracy)514 math_equation_set_accuracy(MathEquation *equation, gint accuracy)
515 {
516 g_return_if_fail(equation != NULL);
517
518 if (mp_serializer_get_trailing_digits(equation->priv->serializer) == accuracy)
519 return;
520 mp_serializer_set_trailing_digits(equation->priv->serializer, accuracy);
521 reformat_display(equation);
522 g_object_notify(G_OBJECT(equation), "accuracy");
523 }
524
525
526 gint
math_equation_get_accuracy(MathEquation * equation)527 math_equation_get_accuracy(MathEquation *equation)
528 {
529 g_return_val_if_fail(equation != NULL, 0);
530
531 return mp_serializer_get_trailing_digits(equation->priv->serializer);
532 }
533
534
535 void
math_equation_set_show_thousands_separators(MathEquation * equation,gboolean visible)536 math_equation_set_show_thousands_separators(MathEquation *equation, gboolean visible)
537 {
538 g_return_if_fail(equation != NULL);
539
540 if (mp_serializer_get_show_thousands_separators(equation->priv->serializer) == visible)
541 return;
542
543 mp_serializer_set_show_thousands_separators(equation->priv->serializer, visible);
544 reformat_display(equation);
545 g_object_notify(G_OBJECT(equation), "show-thousands-separators");
546 }
547
548
549 gboolean
math_equation_get_show_thousands_separators(MathEquation * equation)550 math_equation_get_show_thousands_separators(MathEquation *equation)
551 {
552 g_return_val_if_fail(equation != NULL, FALSE);
553 return mp_serializer_get_show_thousands_separators(equation->priv->serializer);
554 }
555
556
557 void
math_equation_set_show_trailing_zeroes(MathEquation * equation,gboolean visible)558 math_equation_set_show_trailing_zeroes(MathEquation *equation, gboolean visible)
559 {
560 g_return_if_fail(equation != NULL);
561
562 if (mp_serializer_get_show_trailing_zeroes(equation->priv->serializer) == visible)
563 return;
564
565 mp_serializer_set_show_trailing_zeroes(equation->priv->serializer, visible);
566 reformat_display(equation);
567 g_object_notify(G_OBJECT(equation), "show-trailing-zeroes");
568 }
569
570
571 gboolean
math_equation_get_show_trailing_zeroes(MathEquation * equation)572 math_equation_get_show_trailing_zeroes(MathEquation *equation)
573 {
574 g_return_val_if_fail(equation != NULL, FALSE);
575 return mp_serializer_get_show_trailing_zeroes(equation->priv->serializer);
576 }
577
578
579 void
math_equation_set_number_format(MathEquation * equation,MpDisplayFormat format)580 math_equation_set_number_format(MathEquation *equation, MpDisplayFormat format)
581 {
582 g_return_if_fail(equation != NULL);
583
584 if (mp_serializer_get_number_format(equation->priv->serializer) == format)
585 return;
586
587 mp_serializer_set_number_format(equation->priv->serializer, format);
588 reformat_display(equation);
589 g_object_notify(G_OBJECT(equation), "number-format");
590 }
591
592
593 MpDisplayFormat
math_equation_get_number_format(MathEquation * equation)594 math_equation_get_number_format(MathEquation *equation)
595 {
596 g_return_val_if_fail(equation != NULL, MP_DISPLAY_FORMAT_AUTOMATIC);
597 return mp_serializer_get_number_format(equation->priv->serializer);
598 }
599
600
601 void
math_equation_set_base(MathEquation * equation,gint base)602 math_equation_set_base(MathEquation *equation, gint base)
603 {
604 g_return_if_fail(equation != NULL);
605
606 if (mp_serializer_get_base(equation->priv->serializer) == base)
607 return;
608
609 mp_serializer_set_base(equation->priv->serializer, base);
610 reformat_display(equation);
611 g_object_notify(G_OBJECT(equation), "base");
612 }
613
614
615 gint
math_equation_get_base(MathEquation * equation)616 math_equation_get_base(MathEquation *equation)
617 {
618 g_return_val_if_fail(equation != NULL, 10);
619 return mp_serializer_get_base(equation->priv->serializer);
620 }
621
622
623 void
math_equation_set_word_size(MathEquation * equation,gint word_size)624 math_equation_set_word_size(MathEquation *equation, gint word_size)
625 {
626 g_return_if_fail(equation != NULL);
627
628 if (equation->priv->word_size == word_size)
629 return;
630
631 equation->priv->word_size = word_size;
632 g_object_notify(G_OBJECT(equation), "word-size");
633 }
634
635
636 gint
math_equation_get_word_size(MathEquation * equation)637 math_equation_get_word_size(MathEquation *equation)
638 {
639 g_return_val_if_fail(equation != NULL, 64);
640 return equation->priv->word_size;
641 }
642
643
644 void
math_equation_set_angle_units(MathEquation * equation,MPAngleUnit angle_units)645 math_equation_set_angle_units(MathEquation *equation, MPAngleUnit angle_units)
646 {
647 g_return_if_fail(equation != NULL);
648
649 if (equation->priv->angle_units == angle_units)
650 return;
651
652 equation->priv->angle_units = angle_units;
653 g_object_notify(G_OBJECT(equation), "angle-units");
654 }
655
656
657 MPAngleUnit
math_equation_get_angle_units(MathEquation * equation)658 math_equation_get_angle_units(MathEquation *equation)
659 {
660 g_return_val_if_fail(equation != NULL, MP_DEGREES);
661 return equation->priv->angle_units;
662 }
663
664
665 void
math_equation_set_source_currency(MathEquation * equation,const gchar * currency)666 math_equation_set_source_currency(MathEquation *equation, const gchar *currency)
667 {
668 g_return_if_fail(equation != NULL);
669 g_return_if_fail(currency != NULL);
670
671 if (strcmp(equation->priv->source_currency, currency) == 0)
672 return;
673 g_free(equation->priv->source_currency);
674 equation->priv->source_currency = g_strdup(currency);
675 g_settings_set_string(g_settings_var, "source-currency",
676 math_equation_get_source_currency(equation));
677 }
678
679
680 const gchar *
math_equation_get_source_currency(MathEquation * equation)681 math_equation_get_source_currency(MathEquation *equation)
682 {
683 g_return_val_if_fail(equation != NULL, NULL);
684 return equation->priv->source_currency;
685 }
686
687
688 void
math_equation_set_target_currency(MathEquation * equation,const gchar * currency)689 math_equation_set_target_currency(MathEquation *equation, const gchar *currency)
690 {
691 g_return_if_fail(equation != NULL);
692 g_return_if_fail(currency != NULL);
693
694 if (strcmp(equation->priv->target_currency, currency) == 0)
695 return;
696 g_free(equation->priv->target_currency);
697 equation->priv->target_currency = g_strdup(currency);
698 g_settings_set_string(g_settings_var, "target-currency",
699 math_equation_get_target_currency(equation));
700 }
701
702
703 const gchar *
math_equation_get_target_currency(MathEquation * equation)704 math_equation_get_target_currency(MathEquation *equation)
705 {
706 g_return_val_if_fail(equation != NULL, NULL);
707 return equation->priv->target_currency;
708 }
709
710
711 void
math_equation_set_source_units(MathEquation * equation,const gchar * units)712 math_equation_set_source_units(MathEquation *equation, const gchar *units)
713 {
714 g_return_if_fail(equation != NULL);
715 g_return_if_fail(units != NULL);
716
717 if (strcmp(equation->priv->source_units, units) == 0)
718 return;
719
720 g_free(equation->priv->source_units);
721 equation->priv->source_units = g_strdup(units);
722 g_settings_set_string(g_settings_var, "source-units",
723 math_equation_get_source_units(equation));
724 }
725
726 const gchar *
math_equation_get_source_units(MathEquation * equation)727 math_equation_get_source_units(MathEquation *equation)
728 {
729 g_return_val_if_fail(equation != NULL, NULL);
730 return equation->priv->source_units;
731 }
732
733
734 void
math_equation_set_target_units(MathEquation * equation,const gchar * units)735 math_equation_set_target_units(MathEquation *equation, const gchar *units)
736 {
737 g_return_if_fail(equation != NULL);
738 g_return_if_fail(units != NULL);
739
740 if (strcmp(equation->priv->target_units, units) == 0)
741 return;
742
743 g_free(equation->priv->target_units);
744 equation->priv->target_units = g_strdup(units);
745 g_settings_set_string(g_settings_var, "target-units",
746 math_equation_get_target_units(equation));
747 }
748
749
750 const gchar *
math_equation_get_target_units(MathEquation * equation)751 math_equation_get_target_units(MathEquation *equation)
752 {
753 g_return_val_if_fail(equation != NULL, NULL);
754 return equation->priv->target_units;
755 }
756
757
758 void
math_equation_set_status(MathEquation * equation,const gchar * status)759 math_equation_set_status(MathEquation *equation, const gchar *status)
760 {
761 g_return_if_fail(equation != NULL);
762 g_return_if_fail(status != NULL);
763
764 if (strcmp(equation->priv->state.status, status) == 0)
765 return;
766
767 g_free(equation->priv->state.status);
768 equation->priv->state.status = g_strdup(status);
769 g_object_notify(G_OBJECT(equation), "status");
770 }
771
772
773 const gchar *
math_equation_get_status(MathEquation * equation)774 math_equation_get_status(MathEquation *equation)
775 {
776 g_return_val_if_fail(equation != NULL, NULL);
777 return equation->priv->state.status;
778 }
779
780
781 gboolean
math_equation_is_empty(MathEquation * equation)782 math_equation_is_empty(MathEquation *equation)
783 {
784 g_return_val_if_fail(equation != NULL, FALSE);
785 return gtk_text_buffer_get_char_count(GTK_TEXT_BUFFER(equation)) == 0;
786 }
787
788
789 gboolean
math_equation_is_result(MathEquation * equation)790 math_equation_is_result(MathEquation *equation)
791 {
792 char *text;
793 gboolean result;
794
795 g_return_val_if_fail(equation != NULL, FALSE);
796
797 text = math_equation_get_equation(equation);
798 result = strcmp(text, "ans") == 0;
799 g_free(text);
800
801 return result;
802 }
803
804
805 gchar *
math_equation_get_display(MathEquation * equation)806 math_equation_get_display(MathEquation *equation)
807 {
808 GtkTextIter start, end;
809
810 g_return_val_if_fail(equation != NULL, NULL);
811
812 gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(equation), &start, &end);
813 return gtk_text_buffer_get_text(GTK_TEXT_BUFFER(equation), &start, &end, FALSE);
814 }
815
816
817 gchar *
math_equation_get_equation(MathEquation * equation)818 math_equation_get_equation(MathEquation *equation)
819 {
820 gchar *text;
821 GString *eq_text;
822 gint ans_start = -1, ans_end = -1, offset;
823 const gchar *read_iter;
824 gboolean last_is_digit = FALSE;
825
826 g_return_val_if_fail(equation != NULL, NULL);
827
828 text = math_equation_get_display(equation);
829 eq_text = g_string_sized_new(strlen(text));
830
831 if (equation->priv->ans_start)
832 get_ans_offsets(equation, &ans_start, &ans_end);
833
834 for (read_iter = text, offset = 0; *read_iter != '\0'; read_iter = g_utf8_next_char(read_iter), offset++) {
835 gunichar c;
836 gboolean is_digit, next_is_digit;
837
838 c = g_utf8_get_char(read_iter);
839 is_digit = g_unichar_isdigit(c);
840 next_is_digit = g_unichar_isdigit(g_utf8_get_char(g_utf8_next_char(read_iter)));
841
842 /* Replace ans text with variable */
843 if (offset == ans_start) {
844 g_string_append(eq_text, "ans");
845 read_iter = g_utf8_offset_to_pointer(read_iter, ans_end - ans_start - 1);
846 offset += ans_end - ans_start - 1;
847 is_digit = FALSE;
848 continue;
849 }
850
851 /* Ignore thousands separators */
852 if (c == mp_serializer_get_thousands_separator(equation->priv->serializer) && last_is_digit && next_is_digit)
853 ;
854 /* Substitute radix character */
855 else if (c == mp_serializer_get_radix(equation->priv->serializer) && (last_is_digit || next_is_digit))
856 g_string_append_unichar(eq_text, '.');
857 else
858 g_string_append_unichar(eq_text, c);
859
860 last_is_digit = is_digit;
861 }
862 g_free(text);
863
864 text = eq_text->str;
865 g_string_free(eq_text, FALSE);
866
867 return text;
868 }
869
870
871 gboolean
math_equation_get_number(MathEquation * equation,MPNumber * z)872 math_equation_get_number(MathEquation *equation, MPNumber *z)
873 {
874 gchar *text;
875 gboolean result;
876
877 g_return_val_if_fail(equation != NULL, FALSE);
878 g_return_val_if_fail(z != NULL, FALSE);
879
880 if (math_equation_is_result(equation)) {
881 mp_set_from_mp(math_equation_get_answer(equation), z);
882 return TRUE;
883 }
884 else {
885 text = math_equation_get_equation(equation);
886 result = !mp_serializer_from_string(equation->priv->serializer, text, z);
887 g_free(text);
888 return result;
889 }
890 }
891
892
893 MpSerializer *
math_equation_get_serializer(MathEquation * equation)894 math_equation_get_serializer(MathEquation *equation)
895 {
896 g_return_val_if_fail(equation != NULL, NULL);
897 return equation->priv->serializer;
898 }
899
900
901 void
math_equation_set_number_mode(MathEquation * equation,NumberMode mode)902 math_equation_set_number_mode(MathEquation *equation, NumberMode mode)
903 {
904 g_return_if_fail(equation != NULL);
905
906 if (equation->priv->number_mode == mode)
907 return;
908
909 equation->priv->can_super_minus = mode == SUPERSCRIPT;
910
911 equation->priv->number_mode = mode;
912 g_object_notify(G_OBJECT(equation), "number-mode");
913 }
914
915
916 NumberMode
math_equation_get_number_mode(MathEquation * equation)917 math_equation_get_number_mode(MathEquation *equation)
918 {
919 g_return_val_if_fail(equation != NULL, NORMAL);
920 return equation->priv->number_mode;
921 }
922
923
924 gboolean
math_equation_in_solve(MathEquation * equation)925 math_equation_in_solve(MathEquation *equation)
926 {
927 g_return_val_if_fail(equation != NULL, FALSE);
928 return equation->priv->in_solve;
929 }
930
931
932 const MPNumber *
math_equation_get_answer(MathEquation * equation)933 math_equation_get_answer(MathEquation *equation)
934 {
935 g_return_val_if_fail(equation != NULL, FALSE);
936 return &equation->priv->state.ans;
937 }
938
939
940 void
math_equation_store(MathEquation * equation,const gchar * name)941 math_equation_store(MathEquation *equation, const gchar *name)
942 {
943 MPNumber t = mp_new();
944
945 g_return_if_fail(equation != NULL);
946 g_return_if_fail(name != NULL);
947
948 if (!math_equation_get_number(equation, &t))
949 math_equation_set_status(equation, _("No sane value to store"));
950 else
951 math_variables_set(equation->priv->variables, name, &t);
952 mp_clear(&t);
953 }
954
955
956 void
math_equation_recall(MathEquation * equation,const gchar * name)957 math_equation_recall(MathEquation *equation, const gchar *name)
958 {
959 g_return_if_fail(equation != NULL);
960 g_return_if_fail(name != NULL);
961 math_equation_insert(equation, name);
962 }
963
964
965 void
math_equation_set(MathEquation * equation,const gchar * text)966 math_equation_set(MathEquation *equation, const gchar *text)
967 {
968 g_return_if_fail(equation != NULL);
969 g_return_if_fail(text != NULL);
970 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), text, -1);
971 clear_ans(equation, FALSE);
972 }
973
974 static void
math_equation_set_with_history(MathEquation * equation,const gchar * text)975 math_equation_set_with_history(MathEquation *equation, const gchar *text)
976 {
977 MathEquationState *state;
978 MPNumber x = mp_new();
979
980 math_equation_get_number(equation, &x);
981 math_equation_set(equation, text);
982
983 /* Notify history */
984 state = get_current_state(equation);
985 g_signal_emit_by_name(equation, "history", state->expression, &x);
986
987 free_state(state);
988 mp_clear(&x);
989 }
990
991
992 void
math_equation_set_number(MathEquation * equation,const MPNumber * x)993 math_equation_set_number(MathEquation *equation, const MPNumber *x)
994 {
995 char *text;
996 GtkTextIter start, end;
997 MathEquationState *state;
998
999 g_return_if_fail(equation != NULL);
1000 g_return_if_fail(x != NULL);
1001
1002 /* Notify history */
1003 state = get_current_state(equation);
1004 g_signal_emit_by_name(equation, "history", state->expression, x);
1005
1006 /* Show the number in the user chosen format */
1007 text = mp_serializer_to_string(equation->priv->serializer, x);
1008 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), text, -1);
1009 mp_set_from_mp(x, &equation->priv->state.ans);
1010
1011 /* Mark this text as the answer variable */
1012 gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(equation), &start, &end);
1013 clear_ans(equation, FALSE);
1014 equation->priv->ans_start = gtk_text_buffer_create_mark(GTK_TEXT_BUFFER(equation), NULL, &start, FALSE);
1015 equation->priv->ans_end = gtk_text_buffer_create_mark(GTK_TEXT_BUFFER(equation), NULL, &end, TRUE);
1016 gtk_text_buffer_apply_tag(GTK_TEXT_BUFFER(equation), equation->priv->ans_tag, &start, &end);
1017
1018 g_object_notify(G_OBJECT(equation), "display");
1019
1020 g_free(text);
1021 free_state(state);
1022 }
1023
1024
1025 void
math_equation_insert(MathEquation * equation,const gchar * text)1026 math_equation_insert(MathEquation *equation, const gchar *text)
1027 {
1028 g_return_if_fail(equation != NULL);
1029 g_return_if_fail(text != NULL);
1030
1031 /* Replace ** with ^ (not on all keyboards) */
1032 if (!gtk_text_buffer_get_has_selection(GTK_TEXT_BUFFER(equation)) &&
1033 strcmp(text, "×") == 0 && equation->priv->state.entered_multiply) {
1034 GtkTextIter iter;
1035
1036 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, gtk_text_buffer_get_insert(GTK_TEXT_BUFFER(equation)));
1037 gtk_text_buffer_backspace(GTK_TEXT_BUFFER(equation), &iter, TRUE, TRUE);
1038 gtk_text_buffer_insert_at_cursor(GTK_TEXT_BUFFER(equation), "^", -1);
1039 return;
1040 }
1041
1042 /* Can't enter superscript minus after entering digits */
1043 if (strstr("⁰¹²³⁴⁵⁶⁷⁸⁹", text) != NULL || strcmp("⁻", text) == 0)
1044 equation->priv->can_super_minus = FALSE;
1045
1046 /* Disable super/subscript mode when finished entering */
1047 if (strstr("⁻⁰¹²³⁴⁵⁶⁷⁸⁹₀₁₂₃₄₅₆₇₈₉", text) == NULL)
1048 math_equation_set_number_mode(equation, NORMAL);
1049
1050 gtk_text_buffer_delete_selection(GTK_TEXT_BUFFER(equation), FALSE, FALSE);
1051 gtk_text_buffer_insert_at_cursor(GTK_TEXT_BUFFER(equation), text, -1);
1052 }
1053
1054
1055 void
math_equation_insert_digit(MathEquation * equation,guint digit)1056 math_equation_insert_digit(MathEquation *equation, guint digit)
1057 {
1058 static const char *subscript_digits[] = {"₀", "₁", "₂", "₃", "₄", "₅", "₆", "₇", "₈", "₉", NULL};
1059 static const char *superscript_digits[] = {"⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹", NULL};
1060
1061 g_return_if_fail(equation != NULL);
1062 g_return_if_fail(digit < 16);
1063
1064 if (equation->priv->number_mode == NORMAL || digit >= 10) {
1065 gchar buffer[7];
1066 gint len;
1067 len = g_unichar_to_utf8(math_equation_get_digit_text(equation, digit), buffer);
1068 buffer[len] = '\0';
1069 math_equation_insert(equation, buffer);
1070 }
1071 else if (equation->priv->number_mode == SUPERSCRIPT)
1072 math_equation_insert(equation, superscript_digits[digit]);
1073 else if (equation->priv->number_mode == SUBSCRIPT)
1074 math_equation_insert(equation, subscript_digits[digit]);
1075 }
1076
1077
1078 void
math_equation_insert_numeric_point(MathEquation * equation)1079 math_equation_insert_numeric_point(MathEquation *equation)
1080 {
1081 gchar buffer[7];
1082 gint len;
1083
1084 g_return_if_fail(equation != NULL);
1085
1086 len = g_unichar_to_utf8(mp_serializer_get_radix(equation->priv->serializer), buffer);
1087 buffer[len] = '\0';
1088 math_equation_insert(equation, buffer);
1089 }
1090
1091
1092 void
math_equation_insert_number(MathEquation * equation,const MPNumber * x)1093 math_equation_insert_number(MathEquation *equation, const MPNumber *x)
1094 {
1095 char *text;
1096
1097 g_return_if_fail(equation != NULL);
1098 g_return_if_fail(x != NULL);
1099
1100 text = mp_serializer_to_string(equation->priv->serializer, x);
1101 math_equation_insert(equation, text);
1102 g_free(text);
1103 }
1104
1105
1106 void
math_equation_insert_exponent(MathEquation * equation)1107 math_equation_insert_exponent(MathEquation *equation)
1108 {
1109 g_return_if_fail(equation != NULL);
1110 math_equation_insert(equation, "×10");
1111 math_equation_set_number_mode(equation, SUPERSCRIPT);
1112 }
1113
1114
1115 void
math_equation_insert_subtract(MathEquation * equation)1116 math_equation_insert_subtract(MathEquation *equation)
1117 {
1118 g_return_if_fail(equation != NULL);
1119 if (equation->priv->number_mode == SUPERSCRIPT && equation->priv->can_super_minus) {
1120 math_equation_insert(equation, "⁻");
1121 equation->priv->can_super_minus = FALSE;
1122 }
1123 else {
1124 math_equation_insert(equation, "−");
1125 math_equation_set_number_mode(equation, NORMAL);
1126 }
1127 }
1128
1129
1130 static int
variable_is_defined(const char * name,void * data)1131 variable_is_defined(const char *name, void *data)
1132 {
1133 MathEquation *equation = data;
1134 char *c, *lower_name;
1135
1136 lower_name = strdup(name);
1137 for (c = lower_name; *c; c++)
1138 *c = tolower(*c);
1139
1140 if (strcmp(lower_name, "rand") == 0 ||
1141 strcmp(lower_name, "ans") == 0) {
1142 g_free(lower_name);
1143 return 1;
1144 }
1145 g_free(lower_name);
1146
1147 return math_variables_get(equation->priv->variables, name) != NULL;
1148 }
1149
1150
1151 static int
get_variable(const char * name,MPNumber * z,void * data)1152 get_variable(const char *name, MPNumber *z, void *data)
1153 {
1154 char *c, *lower_name;
1155 int result = 1;
1156 MathEquation *equation = data;
1157 MPNumber *t;
1158
1159 lower_name = strdup(name);
1160 for (c = lower_name; *c; c++)
1161 *c = tolower(*c);
1162
1163 if (strcmp(lower_name, "rand") == 0)
1164 mp_set_from_random(z);
1165 else if (strcmp(lower_name, "ans") == 0)
1166 mp_set_from_mp(&equation->priv->state.ans, z);
1167 else {
1168 t = math_variables_get(equation->priv->variables, name);
1169 if (t)
1170 mp_set_from_mp(t, z);
1171 else
1172 result = 0;
1173 }
1174 free(lower_name);
1175
1176 return result;
1177 }
1178
1179
1180 static void
set_variable(const char * name,const MPNumber * x,void * data)1181 set_variable(const char *name, const MPNumber *x, void *data)
1182 {
1183 MathEquation *equation = data;
1184 /* FIXME: Don't allow writing to built-in variables, e.g. ans, rand, sin, ... */
1185 math_variables_set(equation->priv->variables, name, x);
1186 }
1187
1188
1189 static int
convert(const MPNumber * x,const char * x_units,const char * z_units,MPNumber * z,void * data)1190 convert(const MPNumber *x, const char *x_units, const char *z_units, MPNumber *z, void *data)
1191 {
1192 return unit_manager_convert_by_symbol(unit_manager_get_default(), x, x_units, z_units, z);
1193 }
1194
1195
1196 static int
parse(MathEquation * equation,const char * text,MPNumber * z,char ** error_token)1197 parse(MathEquation *equation, const char *text, MPNumber *z, char **error_token)
1198 {
1199 MPEquationOptions options;
1200
1201 memset(&options, 0, sizeof(options));
1202 options.base = mp_serializer_get_base(equation->priv->serializer);
1203 options.wordlen = equation->priv->word_size;
1204 options.angle_units = equation->priv->angle_units;
1205 options.variable_is_defined = variable_is_defined;
1206 options.get_variable = get_variable;
1207 options.set_variable = set_variable;
1208 options.convert = convert;
1209 options.callback_data = equation;
1210
1211 return mp_equation_parse(text, &options, z, error_token);
1212 }
1213
1214
1215 /*
1216 * Executed in separate thread. It is thus not a good idea to write to anything
1217 * in MathEquation but the async queue from here.
1218 */
1219 static gpointer
math_equation_solve_real(gpointer data)1220 math_equation_solve_real(gpointer data)
1221 {
1222 MathEquation *equation = MATH_EQUATION(data);
1223 SolveData *solvedata = g_slice_new0(SolveData);
1224
1225 gint n_brackets = 0, result;
1226 gchar *c, *text, *error_token;
1227 GString *equation_text;
1228 MPNumber z = mp_new();
1229
1230 text = math_equation_get_equation(equation);
1231 equation_text = g_string_new(text);
1232 g_free(text);
1233 /* Count the number of brackets and automatically add missing closing brackets */
1234 for (c = equation_text->str; *c; c++) {
1235 if (*c == '(')
1236 n_brackets++;
1237 else if (*c == ')')
1238 n_brackets--;
1239 }
1240 while (n_brackets > 0) {
1241 g_string_append_c(equation_text, ')');
1242 n_brackets--;
1243 }
1244
1245
1246 result = parse(equation, equation_text->str, &z, &error_token);
1247 g_string_free(equation_text, TRUE);
1248
1249 switch (result) {
1250 case PARSER_ERR_NONE:
1251 solvedata->number_result = g_slice_new(MPNumber);
1252 *solvedata->number_result = mp_new();
1253 mp_set_from_mp(&z, solvedata->number_result);
1254 break;
1255
1256 case PARSER_ERR_OVERFLOW:
1257 solvedata->error = g_strdup(/* Error displayed to user when they perform a bitwise operation on numbers greater than the current word */
1258 _("Overflow. Try a bigger word size"));
1259 break;
1260
1261 case PARSER_ERR_UNKNOWN_VARIABLE:
1262 solvedata->error = g_strdup_printf(/* Error displayed to user when they an unknown variable is entered */
1263 _("Unknown variable '%s'"), error_token);
1264 break;
1265
1266 case PARSER_ERR_UNKNOWN_FUNCTION:
1267 solvedata->error = g_strdup_printf(/* Error displayed to user when an unknown function is entered */
1268 _("Function '%s' is not defined"), error_token);
1269 break;
1270
1271 case PARSER_ERR_UNKNOWN_CONVERSION:
1272 solvedata->error = g_strdup(/* Error displayed to user when an conversion with unknown units is attempted */
1273 _("Unknown conversion"));
1274 break;
1275
1276 case PARSER_ERR_MP:
1277 if (mp_get_error())
1278 solvedata->error = g_strdup(mp_get_error());
1279 else if (error_token)
1280 solvedata->error = g_strdup_printf(/* Uncategorized error. Show error token to user*/
1281 _("Malformed expression at token '%s'"), error_token);
1282 else
1283 solvedata->error = g_strdup (/* Unknown error. */
1284 _("Malformed expression"));
1285 break;
1286
1287 default:
1288 solvedata->error = g_strdup(/* Error displayed to user when they enter an invalid calculation */
1289 _("Malformed expression"));
1290 break;
1291 }
1292 g_async_queue_push(equation->priv->queue, solvedata);
1293 mp_clear(&z);
1294
1295 return NULL;
1296 }
1297
1298
1299 static gboolean
math_equation_show_in_progress(gpointer data)1300 math_equation_show_in_progress(gpointer data)
1301 {
1302 MathEquation *equation = MATH_EQUATION(data);
1303 if (equation->priv->in_solve)
1304 math_equation_set_status(equation, _("Calculating"));
1305 return false;
1306 }
1307
1308
1309 static gboolean
math_equation_look_for_answer(gpointer data)1310 math_equation_look_for_answer(gpointer data)
1311 {
1312 MathEquation *equation = MATH_EQUATION(data);
1313 SolveData *result = g_async_queue_try_pop(equation->priv->queue);
1314
1315 if (result == NULL)
1316 return true;
1317
1318 equation->priv->in_solve = false;
1319
1320 if (!result->error)
1321 math_equation_set_status(equation, "");
1322
1323 if (result->error != NULL) {
1324 math_equation_set_status(equation, result->error);
1325 g_free(result->error);
1326 }
1327 else if (result->number_result != NULL) {
1328 math_equation_set_number(equation, result->number_result);
1329 mp_clear(result->number_result);
1330 g_slice_free(MPNumber, result->number_result);
1331 }
1332 else if (result->text_result != NULL) {
1333 math_equation_set_with_history(equation, result->text_result);
1334 g_free(result->text_result);
1335 }
1336 g_slice_free(SolveData, result);
1337
1338 return false;
1339 }
1340
1341
1342 void
math_equation_solve(MathEquation * equation)1343 math_equation_solve(MathEquation *equation)
1344 {
1345 g_return_if_fail(equation != NULL);
1346
1347 // FIXME: should replace calculation or give error message
1348 if (equation->priv->in_solve)
1349 return;
1350
1351 if (math_equation_is_empty(equation))
1352 return;
1353
1354 /* If showing a result return to the equation that caused it */
1355 // FIXME: Result may not be here due to solve (i.e. the user may have entered "ans")
1356 if (math_equation_is_result(equation)) {
1357 math_equation_undo(equation);
1358 return;
1359 }
1360
1361 equation->priv->in_solve = true;
1362
1363 math_equation_set_number_mode(equation, NORMAL);
1364 g_thread_new("", math_equation_solve_real, equation);
1365
1366 g_timeout_add(50, math_equation_look_for_answer, equation);
1367 g_timeout_add(100, math_equation_show_in_progress, equation);
1368 }
1369
1370
1371 static gpointer
math_equation_factorize_real(gpointer data)1372 math_equation_factorize_real(gpointer data)
1373 {
1374 GString *text;
1375 GList *factors, *factor, *next_factor;
1376 MPNumber x = mp_new();
1377 MathEquation *equation = MATH_EQUATION(data);
1378 SolveData *result = g_slice_new0(SolveData);
1379 MpDisplayFormat format = mp_serializer_get_number_format(equation->priv->serializer);
1380
1381 mp_serializer_set_number_format(equation->priv->serializer, MP_DISPLAY_FORMAT_FIXED);
1382 math_equation_get_number(equation, &x);
1383 factors = mp_factorize(&x);
1384
1385 text = g_string_new("");
1386
1387 int e = 1;
1388 for (factor = factors; factor != NULL; factor = factor->next)
1389 {
1390 gchar *temp;
1391 MPNumber *n;
1392
1393 n = factor->data;
1394 next_factor = factor->next;
1395 if (next_factor != NULL && mp_compare(n, next_factor->data) == 0)
1396 {
1397 e++;
1398 continue;
1399 }
1400 temp = mp_serializer_to_string(equation->priv->serializer, n);
1401 g_string_append(text, temp);
1402
1403 if (e > 1)
1404 {
1405 g_string_append(text, "^");
1406 g_string_append_printf(text, "%d", e);
1407 }
1408 if (factor->next)
1409 g_string_append(text, " × ");
1410
1411 e = 1;
1412
1413 mp_clear(n);
1414 g_slice_free(MPNumber, n);
1415 g_free(temp);
1416 }
1417 g_list_free(factors);
1418
1419 result->text_result = g_strndup(text->str, text->len);
1420 g_async_queue_push(equation->priv->queue, result);
1421 g_string_free(text, TRUE);
1422 mp_clear(&x);
1423
1424 mp_serializer_set_number_format(equation->priv->serializer, format);
1425
1426 return NULL;
1427 }
1428
1429
1430 void
math_equation_factorize(MathEquation * equation)1431 math_equation_factorize(MathEquation *equation)
1432 {
1433 MPNumber x = mp_new();
1434
1435 g_return_if_fail(equation != NULL);
1436
1437 // FIXME: should replace calculation or give error message
1438 if (equation->priv->in_solve)
1439 return;
1440
1441 if (!math_equation_get_number(equation, &x) || !mp_is_integer(&x)) {
1442 /* Error displayed when trying to factorize a non-integer value */
1443 math_equation_set_status(equation, _("Need an integer to factorize"));
1444 mp_clear(&x);
1445 return;
1446 }
1447 mp_clear(&x);
1448 equation->priv->in_solve = true;
1449
1450 g_thread_new("", math_equation_factorize_real, equation);
1451
1452 g_timeout_add(50, math_equation_look_for_answer, equation);
1453 g_timeout_add(100, math_equation_show_in_progress, equation);
1454 }
1455
1456
1457 void
math_equation_delete(MathEquation * equation)1458 math_equation_delete(MathEquation *equation)
1459 {
1460 gint cursor;
1461 GtkTextIter start, end;
1462
1463 g_return_if_fail(equation != NULL);
1464
1465 g_object_get(G_OBJECT(equation), "cursor-position", &cursor, NULL);
1466 if (cursor >= gtk_text_buffer_get_char_count(GTK_TEXT_BUFFER(equation)))
1467 return;
1468
1469 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &start, cursor);
1470 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &end, cursor+1);
1471 gtk_text_buffer_delete(GTK_TEXT_BUFFER(equation), &start, &end);
1472 }
1473
1474
1475 void
math_equation_backspace(MathEquation * equation)1476 math_equation_backspace(MathEquation *equation)
1477 {
1478 g_return_if_fail(equation != NULL);
1479
1480 /* Can't delete empty display */
1481 if (math_equation_is_empty(equation))
1482 return;
1483
1484 if (gtk_text_buffer_get_has_selection(GTK_TEXT_BUFFER(equation)))
1485 gtk_text_buffer_delete_selection(GTK_TEXT_BUFFER(equation), FALSE, FALSE);
1486 else {
1487 GtkTextIter iter;
1488 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, gtk_text_buffer_get_insert(GTK_TEXT_BUFFER(equation)));
1489 gtk_text_buffer_backspace(GTK_TEXT_BUFFER(equation), &iter, TRUE, TRUE);
1490 }
1491 }
1492
1493
1494 void
math_equation_clear(MathEquation * equation)1495 math_equation_clear(MathEquation *equation)
1496 {
1497 g_return_if_fail(equation != NULL);
1498
1499 math_equation_set_number_mode(equation, NORMAL);
1500 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), "", -1);
1501 clear_ans(equation, FALSE);
1502 }
1503
1504
1505 void
math_equation_shift(MathEquation * equation,gint count)1506 math_equation_shift(MathEquation *equation, gint count)
1507 {
1508 MPNumber z = mp_new();
1509
1510 g_return_if_fail(equation != NULL);
1511
1512 if (!math_equation_get_number(equation, &z)) {
1513 math_equation_set_status(equation,
1514 /* This message is displayed in the status bar when a bit
1515 shift operation is performed and the display does not contain a number */
1516 _("No sane value to bitwise shift"));
1517 return;
1518 }
1519
1520 mp_shift(&z, count, &z);
1521 math_equation_set_number(equation, &z);
1522 mp_clear(&z);
1523 }
1524
1525
1526 void
math_equation_toggle_bit(MathEquation * equation,guint bit)1527 math_equation_toggle_bit(MathEquation *equation, guint bit)
1528 {
1529 MPNumber x = mp_new();
1530 guint64 bits;
1531 gboolean result;
1532
1533 g_return_if_fail(equation != NULL);
1534
1535 result = math_equation_get_number(equation, &x);
1536 if (result) {
1537 MPNumber max = mp_new();
1538 mp_set_from_unsigned_integer(G_MAXUINT64, &max);
1539 if (mp_is_negative(&x) || mp_is_greater_than(&x, &max))
1540 result = FALSE;
1541 else
1542 bits = mp_to_unsigned_integer(&x);
1543 mp_clear(&max);
1544 }
1545
1546 if (!result) {
1547 math_equation_set_status(equation,
1548 /* Message displayed when cannot toggle bit in display*/
1549 _("Displayed value not an integer"));
1550 mp_clear(&x);
1551 return;
1552 }
1553
1554 bits ^= (1LL << (63 - bit));
1555
1556 mp_set_from_unsigned_integer(bits, &x);
1557
1558 // FIXME: Only do this if in ans format, otherwise set text in same format as previous number
1559 math_equation_set_number(equation, &x);
1560 mp_clear(&x);
1561 }
1562
1563
1564 static void
math_equation_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1565 math_equation_set_property(GObject *object,
1566 guint prop_id,
1567 const GValue *value,
1568 GParamSpec *pspec)
1569 {
1570 MathEquation *self;
1571
1572 self = MATH_EQUATION(object);
1573
1574 switch (prop_id) {
1575 case PROP_STATUS:
1576 math_equation_set_status(self, g_value_get_string(value));
1577 break;
1578 case PROP_DISPLAY:
1579 math_equation_set(self, g_value_get_string(value));
1580 break;
1581 case PROP_NUMBER_MODE:
1582 math_equation_set_number_mode(self, g_value_get_int(value));
1583 break;
1584 case PROP_ACCURACY:
1585 math_equation_set_accuracy(self, g_value_get_int(value));
1586 break;
1587 case PROP_SHOW_THOUSANDS_SEPARATORS:
1588 math_equation_set_show_thousands_separators(self, g_value_get_boolean(value));
1589 break;
1590 case PROP_SHOW_TRAILING_ZEROES:
1591 math_equation_set_show_trailing_zeroes(self, g_value_get_boolean(value));
1592 break;
1593 case PROP_NUMBER_FORMAT:
1594 math_equation_set_number_format(self, g_value_get_int(value));
1595 break;
1596 case PROP_BASE:
1597 math_equation_set_base(self, g_value_get_int(value));
1598 break;
1599 case PROP_WORD_SIZE:
1600 math_equation_set_word_size(self, g_value_get_int(value));
1601 break;
1602 case PROP_ANGLE_UNITS:
1603 math_equation_set_angle_units(self, g_value_get_int(value));
1604 break;
1605 case PROP_SOURCE_CURRENCY:
1606 math_equation_set_source_currency(self, g_value_get_string(value));
1607 break;
1608 case PROP_TARGET_CURRENCY:
1609 math_equation_set_target_currency(self, g_value_get_string(value));
1610 break;
1611 case PROP_SOURCE_UNITS:
1612 math_equation_set_source_units(self, g_value_get_string(value));
1613 break;
1614 case PROP_TARGET_UNITS:
1615 math_equation_set_target_units(self, g_value_get_string(value));
1616 break;
1617 default:
1618 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
1619 break;
1620 }
1621 }
1622
1623
1624 static void
math_equation_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1625 math_equation_get_property(GObject *object,
1626 guint prop_id,
1627 GValue *value,
1628 GParamSpec *pspec)
1629 {
1630 MathEquation *self;
1631 gchar *text;
1632
1633 self = MATH_EQUATION(object);
1634
1635 switch (prop_id) {
1636 case PROP_STATUS:
1637 g_value_set_string(value, self->priv->state.status);
1638 break;
1639 case PROP_DISPLAY:
1640 text = math_equation_get_display(self);
1641 g_value_set_string(value, text);
1642 g_free(text);
1643 break;
1644 case PROP_EQUATION:
1645 text = math_equation_get_equation(self);
1646 g_value_set_string(value, text);
1647 g_free(text);
1648 break;
1649 case PROP_NUMBER_MODE:
1650 g_value_set_enum(value, self->priv->number_mode);
1651 break;
1652 case PROP_ACCURACY:
1653 g_value_set_int(value, mp_serializer_get_trailing_digits(self->priv->serializer));
1654 break;
1655 case PROP_SHOW_THOUSANDS_SEPARATORS:
1656 g_value_set_boolean(value, mp_serializer_get_show_thousands_separators(self->priv->serializer));
1657 break;
1658 case PROP_SHOW_TRAILING_ZEROES:
1659 g_value_set_boolean(value, mp_serializer_get_show_trailing_zeroes(self->priv->serializer));
1660 break;
1661 case PROP_NUMBER_FORMAT:
1662 g_value_set_enum(value, mp_serializer_get_number_format(self->priv->serializer));
1663 break;
1664 case PROP_BASE:
1665 g_value_set_int(value, math_equation_get_base(self));
1666 break;
1667 case PROP_WORD_SIZE:
1668 g_value_set_int(value, self->priv->word_size);
1669 break;
1670 case PROP_ANGLE_UNITS:
1671 g_value_set_enum(value, self->priv->angle_units);
1672 break;
1673 case PROP_SOURCE_CURRENCY:
1674 g_value_set_string(value, self->priv->source_currency);
1675 break;
1676 case PROP_TARGET_CURRENCY:
1677 g_value_set_string(value, self->priv->target_currency);
1678 break;
1679 case PROP_SOURCE_UNITS:
1680 g_value_set_string(value, self->priv->source_units);
1681 break;
1682 case PROP_TARGET_UNITS:
1683 g_value_set_string(value, self->priv->target_units);
1684 break;
1685 case PROP_SERIALIZER:
1686 g_value_set_object(value, self->priv->serializer);
1687 break;
1688 default:
1689 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
1690 break;
1691 }
1692 }
1693
1694
1695 static void
math_equation_constructed(GObject * object)1696 math_equation_constructed(GObject *object)
1697 {
1698 GtkTextBuffer *parent_class;
1699 parent_class = g_type_class_peek_parent(MATH_EQUATION_GET_CLASS(object));
1700 if (G_OBJECT_CLASS(parent_class)->constructed)
1701 G_OBJECT_CLASS(parent_class)->constructed(object);
1702
1703 MATH_EQUATION(object)->priv->ans_tag = gtk_text_buffer_create_tag(GTK_TEXT_BUFFER(object), NULL, "weight", PANGO_WEIGHT_BOLD, NULL);
1704 }
1705
1706
1707 static void
math_equation_class_init(MathEquationClass * klass)1708 math_equation_class_init(MathEquationClass *klass)
1709 {
1710 static GEnumValue number_mode_values[] =
1711 {
1712 {NORMAL, "normal", "normal"},
1713 {SUPERSCRIPT, "superscript", "superscript"},
1714 {SUBSCRIPT, "subscript", "subscript"},
1715 {0, NULL, NULL}
1716 };
1717 static GEnumValue angle_unit_values[] =
1718 {
1719 {MP_RADIANS, "radians", "radians"},
1720 {MP_DEGREES, "degrees", "degrees"},
1721 {MP_GRADIANS, "gradians", "gradians"},
1722 {0, NULL, NULL}
1723 };
1724 GObjectClass *object_class = G_OBJECT_CLASS(klass);
1725
1726 object_class->get_property = math_equation_get_property;
1727 object_class->set_property = math_equation_set_property;
1728 object_class->constructed = math_equation_constructed;
1729
1730 number_mode_type = g_enum_register_static("NumberMode", number_mode_values);
1731 number_format_type = math_mp_display_format_get_type();
1732 angle_unit_type = g_enum_register_static("AngleUnit", angle_unit_values);
1733
1734 g_object_class_install_property(object_class,
1735 PROP_STATUS,
1736 g_param_spec_string("status",
1737 "status",
1738 "Equation status",
1739 "",
1740 G_PARAM_READWRITE));
1741 g_object_class_install_property(object_class,
1742 PROP_DISPLAY,
1743 g_param_spec_string("display",
1744 "display",
1745 "Displayed equation text",
1746 "",
1747 G_PARAM_READWRITE));
1748 g_object_class_install_property(object_class,
1749 PROP_EQUATION,
1750 g_param_spec_string("equation",
1751 "equation",
1752 "Equation text",
1753 "",
1754 G_PARAM_READABLE));
1755 g_object_class_install_property(object_class,
1756 PROP_NUMBER_MODE,
1757 g_param_spec_enum("number-mode",
1758 "number-mode",
1759 "Input number mode",
1760 number_mode_type,
1761 NORMAL,
1762 G_PARAM_READWRITE));
1763 g_object_class_install_property(object_class,
1764 PROP_ACCURACY,
1765 g_param_spec_int("accuracy",
1766 "accuracy",
1767 "Display accuracy",
1768 0, 20, 9,
1769 G_PARAM_READWRITE));
1770 g_object_class_install_property(object_class,
1771 PROP_SHOW_THOUSANDS_SEPARATORS,
1772 g_param_spec_boolean("show-thousands-separators",
1773 "show-thousands-separators",
1774 "Show thousands separators",
1775 TRUE,
1776 G_PARAM_READWRITE));
1777 g_object_class_install_property(object_class,
1778 PROP_SHOW_TRAILING_ZEROES,
1779 g_param_spec_boolean("show-trailing-zeroes",
1780 "show-trailing-zeroes",
1781 "Show trailing zeroes",
1782 FALSE,
1783 G_PARAM_READWRITE));
1784 g_object_class_install_property(object_class,
1785 PROP_NUMBER_FORMAT,
1786 g_param_spec_enum("number-format",
1787 "number-format",
1788 "Display format",
1789 number_format_type,
1790 MP_DISPLAY_FORMAT_FIXED,
1791 G_PARAM_READWRITE));
1792 g_object_class_install_property(object_class,
1793 PROP_BASE,
1794 g_param_spec_int("base",
1795 "base",
1796 "Default number base (derived from number-format)",
1797 2, 16, 10,
1798 G_PARAM_READWRITE));
1799 g_object_class_install_property(object_class,
1800 PROP_WORD_SIZE,
1801 g_param_spec_int("word-size",
1802 "word-size",
1803 "Word size in bits",
1804 8, 64, 64,
1805 G_PARAM_READWRITE));
1806 g_object_class_install_property(object_class,
1807 PROP_ANGLE_UNITS,
1808 g_param_spec_enum("angle-units",
1809 "angle-units",
1810 "Angle units",
1811 angle_unit_type,
1812 MP_DEGREES,
1813 G_PARAM_READWRITE));
1814 g_object_class_install_property(object_class,
1815 PROP_SOURCE_CURRENCY,
1816 g_param_spec_string("source-currency",
1817 "source-currency",
1818 "Source Currency",
1819 "",
1820 G_PARAM_READWRITE));
1821 g_object_class_install_property(object_class,
1822 PROP_TARGET_CURRENCY,
1823 g_param_spec_string("target-currency",
1824 "target-currency",
1825 "target Currency",
1826 "",
1827 G_PARAM_READWRITE));
1828 g_object_class_install_property(object_class,
1829 PROP_SOURCE_UNITS,
1830 g_param_spec_string("source-units",
1831 "source-units",
1832 "Source Units",
1833 "",
1834 G_PARAM_READWRITE));
1835 g_object_class_install_property(object_class,
1836 PROP_TARGET_UNITS,
1837 g_param_spec_string("target-units",
1838 "target-units",
1839 "target Units",
1840 "",
1841 G_PARAM_READWRITE));
1842 g_object_class_install_property(object_class,
1843 PROP_SERIALIZER,
1844 g_param_spec_object("serializer",
1845 "serializer",
1846 "Serializer",
1847 MP_TYPE_SERIALIZER,
1848 G_PARAM_READABLE));
1849
1850 GType param_types[2] = {G_TYPE_STRING, G_TYPE_POINTER};
1851 g_signal_newv("history",
1852 G_TYPE_FROM_CLASS(klass),
1853 G_SIGNAL_RUN_LAST,
1854 0,
1855 NULL,
1856 NULL,
1857 NULL,
1858 G_TYPE_NONE,
1859 2,
1860 param_types);
1861
1862 g_signal_new ("display-changed",
1863 G_TYPE_FROM_CLASS(klass),
1864 G_SIGNAL_RUN_LAST,
1865 0,
1866 NULL,
1867 NULL,
1868 NULL,
1869 G_TYPE_NONE,
1870 0,
1871 NULL);
1872 }
1873
1874
1875 static void
pre_insert_text_cb(MathEquation * equation,GtkTextIter * location,gchar * text,gint len,gpointer user_data)1876 pre_insert_text_cb(MathEquation *equation,
1877 GtkTextIter *location,
1878 gchar *text,
1879 gint len,
1880 gpointer user_data)
1881 {
1882 gunichar c;
1883 gint cursor;
1884
1885 if (equation->priv->in_reformat)
1886 return;
1887
1888 /* If following a delete then have already pushed undo stack (GtkTextBuffer
1889 doesn't indicate replace operations so we have to infer them) */
1890 if (!equation->priv->in_delete)
1891 math_equation_push_undo_stack(equation);
1892
1893 /* Clear result on next digit entered if cursor at end of line */
1894 c = g_utf8_get_char(text);
1895 g_object_get(G_OBJECT(equation), "cursor-position", &cursor, NULL);
1896 if ((g_unichar_isdigit(c) || c == mp_serializer_get_radix(equation->priv->serializer)) &&
1897 math_equation_is_result(equation) &&
1898 cursor >= gtk_text_buffer_get_char_count(GTK_TEXT_BUFFER(equation))) {
1899 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), "", -1);
1900 clear_ans(equation, FALSE);
1901 gtk_text_buffer_get_end_iter(GTK_TEXT_BUFFER(equation), location);
1902 }
1903
1904 if (equation->priv->ans_start) {
1905 gint ans_start, ans_end;
1906 gint offset;
1907
1908 offset = gtk_text_iter_get_offset(location);
1909 get_ans_offsets(equation, &ans_start, &ans_end);
1910
1911 /* Inserted inside or right before ans */
1912 if (offset >= ans_start && offset < ans_end)
1913 clear_ans(equation, TRUE);
1914 }
1915 }
1916
1917
1918 static gboolean
on_delete(MathEquation * equation)1919 on_delete(MathEquation *equation)
1920 {
1921 equation->priv->in_delete = FALSE;
1922 return FALSE;
1923 }
1924
1925
1926 static void
pre_delete_range_cb(MathEquation * equation,GtkTextIter * start,GtkTextIter * end,gpointer user_data)1927 pre_delete_range_cb(MathEquation *equation,
1928 GtkTextIter *start,
1929 GtkTextIter *end,
1930 gpointer user_data)
1931 {
1932 if (equation->priv->in_reformat)
1933 return;
1934
1935 math_equation_push_undo_stack(equation);
1936
1937 equation->priv->in_delete = TRUE;
1938 g_idle_add((GSourceFunc)on_delete, equation);
1939
1940 if (equation->priv->ans_start) {
1941 gint ans_start, ans_end;
1942 gint start_offset, end_offset;
1943
1944 start_offset = gtk_text_iter_get_offset(start);
1945 end_offset = gtk_text_iter_get_offset(end);
1946 get_ans_offsets(equation, &ans_start, &ans_end);
1947
1948 /* Deleted part of ans */
1949 if (start_offset < ans_end && end_offset > ans_start)
1950 clear_ans(equation, TRUE);
1951 }
1952 }
1953
1954
1955 static void
insert_text_cb(MathEquation * equation,GtkTextIter * location,gchar * text,gint len,gpointer user_data)1956 insert_text_cb(MathEquation *equation,
1957 GtkTextIter *location,
1958 gchar *text,
1959 gint len,
1960 gpointer user_data)
1961 {
1962 if (equation->priv->in_reformat)
1963 return;
1964
1965 equation->priv->state.entered_multiply = strcmp(text, "×") == 0;
1966
1967 /* Update thousands separators */
1968 reformat_separators(equation);
1969
1970 g_object_notify(G_OBJECT(equation), "display");
1971 }
1972
1973
1974 static void
delete_range_cb(MathEquation * equation,GtkTextIter * start,GtkTextIter * end,gpointer user_data)1975 delete_range_cb(MathEquation *equation,
1976 GtkTextIter *start,
1977 GtkTextIter *end,
1978 gpointer user_data)
1979 {
1980 if (equation->priv->in_reformat)
1981 return;
1982
1983 equation->priv->state.entered_multiply = FALSE;
1984
1985 /* Update thousands separators */
1986 reformat_separators(equation);
1987
1988 // FIXME: A replace will emit this both for delete-range and insert-text, can it be avoided?
1989 g_object_notify(G_OBJECT(equation), "display");
1990 }
1991
1992
1993 static void
math_equation_init(MathEquation * equation)1994 math_equation_init(MathEquation *equation)
1995 {
1996 /* Digits localized for the given language */
1997 const char *digit_values = _("0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F");
1998 const char *default_digits[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"};
1999 gchar **digits;
2000 /* Default to using untranslated digits, this is because it doesn't make sense in most languages and we need to make this optional.
2001 * See https://bugzilla.gnome.org/show_bug.cgi?id=632661 */
2002 gboolean use_default_digits = TRUE;
2003 int i;
2004
2005 equation->priv = math_equation_get_instance_private (equation);
2006
2007 g_signal_connect(equation, "insert-text", G_CALLBACK(pre_insert_text_cb), equation);
2008 g_signal_connect(equation, "delete-range", G_CALLBACK(pre_delete_range_cb), equation);
2009 g_signal_connect_after(equation, "insert-text", G_CALLBACK(insert_text_cb), equation);
2010 g_signal_connect_after(equation, "delete-range", G_CALLBACK(delete_range_cb), equation);
2011
2012 digits = g_strsplit(digit_values, ",", -1);
2013 for (i = 0; i < 16; i++) {
2014 if (use_default_digits || digits[i] == NULL) {
2015 use_default_digits = TRUE;
2016 equation->priv->digits[i] = g_utf8_get_char(default_digits[i]);
2017 }
2018 else
2019 equation->priv->digits[i] = g_utf8_get_char(digits[i]);
2020 }
2021 g_strfreev(digits);
2022
2023 equation->priv->variables = math_variables_new();
2024
2025 equation->priv->state.ans = mp_new();
2026 equation->priv->state.status = g_strdup("");
2027 equation->priv->word_size = 32;
2028 equation->priv->angle_units = MP_DEGREES;
2029 // FIXME: Pick based on locale
2030 equation->priv->source_currency = g_strdup("");
2031 equation->priv->target_currency = g_strdup("");
2032 equation->priv->source_units = g_strdup("");
2033 equation->priv->target_units = g_strdup("");
2034 equation->priv->serializer = mp_serializer_new(MP_DISPLAY_FORMAT_AUTOMATIC, 10, 9);
2035 equation->priv->queue = g_async_queue_new();
2036
2037 mp_set_from_integer(0, &equation->priv->state.ans);
2038 }
2039