Merge currency and units together, drop old financial conversion bar and use generic...
[gcalctool.git] / src / math-equation.c
blobc76b7748832d901ef076525bf5d338e1bcbcb6c5
1 /* Copyright (c) 1987-2008 Sun Microsystems, Inc. All Rights Reserved.
2 * Copyright (c) 2008-2009 Robert Ancell
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2, or (at your option)
7 * any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
17 * 02111-1307, USA.
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <ctype.h>
24 #include <math.h>
25 #include <errno.h>
26 #include <glib.h>
28 #include "math-equation.h"
30 #include "mp.h"
31 #include "mp-equation.h"
32 #include "mp-serializer.h"
33 #include "currency.h"
34 #include "math-enums.h"
37 enum {
38 PROP_0,
39 PROP_STATUS,
40 PROP_DISPLAY,
41 PROP_EQUATION,
42 PROP_NUMBER_MODE,
43 PROP_ACCURACY,
44 PROP_SHOW_THOUSANDS_SEPARATORS,
45 PROP_SHOW_TRAILING_ZEROES,
46 PROP_NUMBER_FORMAT,
47 PROP_BASE,
48 PROP_WORD_SIZE,
49 PROP_ANGLE_UNITS,
50 PROP_SOURCE_CURRENCY,
51 PROP_TARGET_CURRENCY,
52 PROP_SOURCE_UNITS,
53 PROP_TARGET_UNITS,
54 PROP_SERIALIZER
57 static GType number_mode_type, number_format_type, angle_unit_type;
59 #define MAX_DIGITS 512
61 /* Expression mode state */
62 typedef struct {
63 MPNumber ans; /* Previously calculated answer */
64 gchar *expression; /* Expression entered by user */
65 gint ans_start, ans_end; /* Start and end characters for ans variable in expression */
66 gint cursor; /* ??? */
67 NumberMode number_mode; /* ??? */
68 gboolean can_super_minus; /* TRUE if entering minus can generate a superscript minus */
69 gboolean entered_multiply; /* Last insert was a multiply character */
70 gchar *status; /* Equation status */
71 } MathEquationState;
73 struct MathEquationPrivate
75 GtkTextTag *ans_tag;
77 gint word_size; /* Word size in bits */
78 MPAngleUnit angle_units; /* Units for trigonometric functions */
79 char *source_currency;
80 char *target_currency;
81 char *source_units;
82 char *target_units;
83 NumberMode number_mode; /* ??? */
84 gboolean can_super_minus; /* TRUE if entering minus can generate a superscript minus */
86 gunichar digits[16]; /* Localized digits */
88 GtkTextMark *ans_start, *ans_end;
90 MathEquationState state; /* Equation state */
91 GList *undo_stack; /* History of expression mode states */
92 GList *redo_stack;
93 gboolean in_undo_operation;
95 gboolean in_reformat;
97 gboolean in_delete;
99 gboolean in_solve;
101 MathVariables *variables;
102 UnitManager *unit_manager;
103 MpSerializer *serializer;
105 GAsyncQueue *queue;
108 typedef struct {
109 MPNumber *number_result;
110 gchar *text_result;
111 gchar *error;
112 } SolveData;
114 G_DEFINE_TYPE (MathEquation, math_equation, GTK_TYPE_TEXT_BUFFER);
117 MathEquation *
118 math_equation_new()
120 return g_object_new(math_equation_get_type(), NULL);
124 MathVariables *
125 math_equation_get_variables(MathEquation *equation)
127 return equation->priv->variables;
131 UnitManager *
132 math_equation_get_unit_manager(MathEquation *equation)
134 return equation->priv->unit_manager;
138 static void
139 get_ans_offsets(MathEquation *equation, gint *start, gint *end)
141 GtkTextIter iter;
143 if (!equation->priv->ans_start) {
144 *start = *end = -1;
145 return;
148 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, equation->priv->ans_start);
149 *start = gtk_text_iter_get_offset(&iter);
150 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, equation->priv->ans_end);
151 *end = gtk_text_iter_get_offset(&iter);
155 static void
156 reformat_ans(MathEquation *equation)
158 if (!equation->priv->ans_start)
159 return;
161 gchar *orig_ans_text;
162 gchar *ans_text;
163 GtkTextIter ans_start, ans_end;
165 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &ans_start, equation->priv->ans_start);
166 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &ans_end, equation->priv->ans_end);
167 orig_ans_text = gtk_text_buffer_get_text(GTK_TEXT_BUFFER(equation), &ans_start, &ans_end, FALSE);
168 ans_text = mp_serializer_to_string(equation->priv->serializer, &equation->priv->state.ans);
169 if (strcmp(orig_ans_text, ans_text) != 0) {
170 gint start;
172 equation->priv->in_undo_operation = TRUE;
173 equation->priv->in_reformat = TRUE;
175 start = gtk_text_iter_get_offset(&ans_start);
176 gtk_text_buffer_delete(GTK_TEXT_BUFFER(equation), &ans_start, &ans_end);
177 gtk_text_buffer_insert_with_tags(GTK_TEXT_BUFFER(equation), &ans_end, ans_text, -1, equation->priv->ans_tag, NULL);
179 /* There seems to be a bug in the marks as they alternate being the correct and incorrect ways. Reset them */
180 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &ans_start, start);
181 gtk_text_buffer_move_mark(GTK_TEXT_BUFFER(equation), equation->priv->ans_start, &ans_start);
182 gtk_text_buffer_move_mark(GTK_TEXT_BUFFER(equation), equation->priv->ans_end, &ans_end);
184 equation->priv->in_reformat = FALSE;
185 equation->priv->in_undo_operation = FALSE;
187 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &ans_start, equation->priv->ans_start);
188 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &ans_end, equation->priv->ans_end);
189 g_free(orig_ans_text);
190 g_free(ans_text);
194 static gint
195 count_digits(MathEquation *equation, const gchar *text)
197 const gchar *read_iter;
198 gint count = 0;
200 read_iter = text;
201 while (*read_iter != '\0') {
202 if (!g_unichar_isdigit(g_utf8_get_char(read_iter)))
203 return count;
205 read_iter = g_utf8_next_char(read_iter);
207 /* Allow a thousands separator between digits follow a digit */
208 if (g_utf8_get_char(read_iter) == mp_serializer_get_thousands_separator(equation->priv->serializer)) {
209 read_iter = g_utf8_next_char(read_iter);
210 if (!g_unichar_isdigit(g_utf8_get_char(read_iter)))
211 return count;
214 count++;
217 return count;
221 static void
222 reformat_separators(MathEquation *equation)
224 gchar *text, *read_iter;
225 gint ans_start, ans_end;
226 gint offset, digit_offset = 0;
227 gboolean in_number = FALSE, in_radix = FALSE, last_is_tsep = FALSE;
229 equation->priv->in_undo_operation = TRUE;
230 equation->priv->in_reformat = TRUE;
232 text = math_equation_get_display(equation);
233 get_ans_offsets(equation, &ans_start, &ans_end);
234 for (read_iter = text, offset = 0; *read_iter != '\0'; read_iter = g_utf8_next_char(read_iter), offset++) {
235 gunichar c;
236 gboolean expect_tsep;
238 /* See what digit this character is */
239 c = g_utf8_get_char(read_iter);
241 expect_tsep = mp_serializer_get_show_thousands_separators(equation->priv->serializer) &&
242 in_number && !in_radix && !last_is_tsep &&
243 digit_offset > 0 && digit_offset % mp_serializer_get_thousands_separator_count(equation->priv->serializer) == 0;
244 last_is_tsep = FALSE;
246 /* Don't mess with ans */
247 if (offset >= ans_start && offset <= ans_end) {
248 in_number = in_radix = FALSE;
249 continue;
251 if (g_unichar_isdigit(c)) {
252 if (!in_number)
253 digit_offset = count_digits(equation, read_iter);
254 in_number = TRUE;
256 /* Expected a thousands separator between these digits - insert it */
257 if (expect_tsep) {
258 GtkTextIter iter;
259 gchar buffer[7];
260 gint len;
262 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &iter, offset);
263 len = g_unichar_to_utf8(mp_serializer_get_thousands_separator(equation->priv->serializer), buffer);
264 buffer[len] = '\0';
265 gtk_text_buffer_insert(GTK_TEXT_BUFFER(equation), &iter, buffer, -1);
266 offset++;
267 last_is_tsep = TRUE;
270 digit_offset--;
272 else if (c == mp_serializer_get_radix(equation->priv->serializer)) {
273 in_number = in_radix = TRUE;
275 else if (c == mp_serializer_get_thousands_separator(equation->priv->serializer)) {
276 /* Didn't expect thousands separator - delete it */
277 if (!expect_tsep && in_number) {
278 GtkTextIter start, end;
279 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &start, offset);
280 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &end, offset + 1);
281 gtk_text_buffer_delete(GTK_TEXT_BUFFER(equation), &start, &end);
282 offset--;
284 else
285 last_is_tsep = TRUE;
287 else {
288 in_number = in_radix = FALSE;
292 g_free(text);
294 equation->priv->in_reformat = FALSE;
295 equation->priv->in_undo_operation = FALSE;
299 static void
300 reformat_display(MathEquation *equation, gint old_base)
302 /* Change ans */
303 reformat_ans(equation);
305 /* Add/remove thousands separators */
306 reformat_separators(equation);
310 static MathEquationState *
311 get_current_state(MathEquation *equation)
313 MathEquationState *state;
314 gint ans_start = -1, ans_end = -1;
316 state = g_malloc0(sizeof(MathEquationState));
318 if (equation->priv->ans_start)
320 GtkTextIter iter;
321 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, equation->priv->ans_start);
322 ans_start = gtk_text_iter_get_offset(&iter);
323 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, equation->priv->ans_end);
324 ans_end = gtk_text_iter_get_offset(&iter);
327 mp_set_from_mp(&equation->priv->state.ans, &state->ans);
328 state->expression = math_equation_get_display(equation);
329 state->ans_start = ans_start;
330 state->ans_end = ans_end;
331 g_object_get(G_OBJECT(equation), "cursor-position", &state->cursor, NULL);
332 state->number_mode = equation->priv->number_mode;
333 state->can_super_minus = equation->priv->can_super_minus;
334 state->entered_multiply = equation->priv->state.entered_multiply;
335 state->status = g_strdup(equation->priv->state.status);
337 return state;
341 static void
342 free_state(MathEquationState *state)
344 g_free(state->expression);
345 g_free(state->status);
346 g_free(state);
350 static void
351 math_equation_push_undo_stack(MathEquation *equation)
353 GList *link;
354 MathEquationState *state;
356 if (equation->priv->in_undo_operation)
357 return;
359 math_equation_set_status(equation, "");
361 /* Can't redo anymore */
362 for (link = equation->priv->redo_stack; link; link = link->next) {
363 state = link->data;
364 free_state(state);
366 g_list_free(equation->priv->redo_stack);
367 equation->priv->redo_stack = NULL;
369 state = get_current_state(equation);
370 equation->priv->undo_stack = g_list_prepend(equation->priv->undo_stack, state);
374 static void
375 clear_ans(MathEquation *equation, gboolean remove_tag)
377 if (!equation->priv->ans_start)
378 return;
380 if (remove_tag) {
381 GtkTextIter start, end;
383 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &start, equation->priv->ans_start);
384 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &end, equation->priv->ans_end);
385 gtk_text_buffer_remove_tag(GTK_TEXT_BUFFER(equation), equation->priv->ans_tag, &start, &end);
388 gtk_text_buffer_delete_mark(GTK_TEXT_BUFFER(equation), equation->priv->ans_start);
389 gtk_text_buffer_delete_mark(GTK_TEXT_BUFFER(equation), equation->priv->ans_end);
390 equation->priv->ans_start = NULL;
391 equation->priv->ans_end = NULL;
395 static void
396 apply_state(MathEquation *equation, MathEquationState *state)
398 GtkTextIter cursor;
400 /* Disable undo detection */
401 equation->priv->in_undo_operation = TRUE;
403 mp_set_from_mp(&state->ans, &equation->priv->state.ans);
405 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), state->expression, -1);
406 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &cursor, state->cursor);
407 gtk_text_buffer_place_cursor(GTK_TEXT_BUFFER(equation), &cursor);
408 clear_ans(equation, FALSE);
409 if (state->ans_start >= 0) {
410 GtkTextIter start, end;
412 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &start, state->ans_start);
413 equation->priv->ans_start = gtk_text_buffer_create_mark(GTK_TEXT_BUFFER(equation), NULL, &start, FALSE);
414 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &end, state->ans_end);
415 equation->priv->ans_end = gtk_text_buffer_create_mark(GTK_TEXT_BUFFER(equation), NULL, &end, TRUE);
416 gtk_text_buffer_apply_tag(GTK_TEXT_BUFFER(equation), equation->priv->ans_tag, &start, &end);
419 math_equation_set_number_mode(equation, state->number_mode);
420 equation->priv->can_super_minus = state->can_super_minus;
421 equation->priv->state.entered_multiply = state->entered_multiply;
422 math_equation_set_status(equation, state->status);
424 equation->priv->in_undo_operation = FALSE;
428 void
429 math_equation_copy(MathEquation *equation)
431 GtkTextIter start, end;
432 gchar *text;
434 if (!gtk_text_buffer_get_selection_bounds(GTK_TEXT_BUFFER(equation), &start, &end))
435 gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(equation), &start, &end);
437 text = gtk_text_buffer_get_text(GTK_TEXT_BUFFER(equation), &start, &end, FALSE);
438 gtk_clipboard_set_text(gtk_clipboard_get(GDK_NONE), text, -1);
439 g_free(text);
443 static void
444 on_paste(GtkClipboard *clipboard, const gchar *text, gpointer data)
446 MathEquation *equation = data;
447 if (text != NULL)
448 math_equation_insert(equation, text);
452 void
453 math_equation_paste(MathEquation *equation)
455 gtk_clipboard_request_text(gtk_clipboard_get(GDK_NONE), on_paste, equation);
459 void
460 math_equation_undo(MathEquation *equation)
462 GList *link;
463 MathEquationState *state;
465 if (!equation->priv->undo_stack) {
466 math_equation_set_status(equation,
467 /* Error shown when trying to undo with no undo history */
468 _("No undo history"));
469 return;
472 link = equation->priv->undo_stack;
473 equation->priv->undo_stack = g_list_remove_link(equation->priv->undo_stack, link);
474 state = link->data;
475 g_list_free(link);
477 equation->priv->redo_stack = g_list_prepend(equation->priv->redo_stack, get_current_state(equation));
479 apply_state(equation, state);
480 free_state(state);
484 void
485 math_equation_redo(MathEquation *equation)
487 GList *link;
488 MathEquationState *state;
490 if (!equation->priv->redo_stack) {
491 math_equation_set_status(equation,
492 /* Error shown when trying to redo with no redo history */
493 _("No redo history"));
494 return;
497 link = equation->priv->redo_stack;
498 equation->priv->redo_stack = g_list_remove_link(equation->priv->redo_stack, link);
499 state = link->data;
500 g_list_free(link);
502 equation->priv->undo_stack = g_list_prepend(equation->priv->undo_stack, get_current_state(equation));
504 apply_state(equation, state);
505 free_state(state);
509 gunichar
510 math_equation_get_digit_text(MathEquation *equation, guint digit)
512 return equation->priv->digits[digit];
516 void
517 math_equation_set_accuracy(MathEquation *equation, gint accuracy)
519 gint old_accuracy = mp_serializer_get_accuracy(equation->priv->serializer);
520 if (old_accuracy == accuracy)
521 return;
522 mp_serializer_set_accuracy(equation->priv->serializer, accuracy);
523 reformat_display(equation, mp_serializer_get_base(equation->priv->serializer));
524 g_object_notify(G_OBJECT(equation), "accuracy");
528 gint
529 math_equation_get_accuracy(MathEquation *equation)
531 return mp_serializer_get_accuracy(equation->priv->serializer);
535 void
536 math_equation_set_show_thousands_separators(MathEquation *equation, gboolean visible)
538 gboolean old_visible = mp_serializer_get_show_thousands_separators(equation->priv->serializer);
539 if (old_visible == visible)
540 return;
541 mp_serializer_set_show_thousands_separators(equation->priv->serializer, visible);
542 reformat_display(equation, mp_serializer_get_base(equation->priv->serializer));
543 g_object_notify(G_OBJECT(equation), "show-thousands-separators");
547 gboolean
548 math_equation_get_show_thousands_separators(MathEquation *equation)
550 return mp_serializer_get_show_thousands_separators(equation->priv->serializer);
554 void
555 math_equation_set_show_trailing_zeroes(MathEquation *equation, gboolean visible)
557 gboolean old_visible = mp_serializer_get_show_trailing_zeroes(equation->priv->serializer);
558 if (old_visible == visible)
559 return;
560 mp_serializer_set_show_trailing_zeroes(equation->priv->serializer, visible);
561 reformat_display(equation, mp_serializer_get_base(equation->priv->serializer));
562 g_object_notify(G_OBJECT(equation), "show-trailing-zeroes");
566 gboolean
567 math_equation_get_show_trailing_zeroes(MathEquation *equation)
569 return mp_serializer_get_show_trailing_zeroes(equation->priv->serializer);
573 void
574 math_equation_set_number_format(MathEquation *equation, MpDisplayFormat format)
576 MpDisplayFormat old_format = mp_serializer_get_number_format(equation->priv->serializer);
577 if (old_format == format)
578 return;
580 mp_serializer_set_number_format(equation->priv->serializer, format);
581 reformat_display(equation, mp_serializer_get_base(equation->priv->serializer));
582 g_object_notify(G_OBJECT(equation), "number-format");
586 MpDisplayFormat
587 math_equation_get_number_format(MathEquation *equation)
589 return mp_serializer_get_number_format(equation->priv->serializer);
593 void
594 math_equation_set_base(MathEquation *equation, gint base)
596 gint old_base = mp_serializer_get_base(equation->priv->serializer);
598 if (old_base == base)
599 return;
601 mp_serializer_set_base(equation->priv->serializer, base);
602 reformat_display(equation, old_base);
603 g_object_notify(G_OBJECT(equation), "base");
607 gint
608 math_equation_get_base(MathEquation *equation)
610 return mp_serializer_get_base(equation->priv->serializer);
614 void
615 math_equation_set_word_size(MathEquation *equation, gint word_size)
617 if (equation->priv->word_size == word_size)
618 return;
619 equation->priv->word_size = word_size;
620 g_object_notify(G_OBJECT(equation), "word-size");
624 gint
625 math_equation_get_word_size(MathEquation *equation)
627 return equation->priv->word_size;
631 void
632 math_equation_set_angle_units(MathEquation *equation, MPAngleUnit angle_units)
634 if (equation->priv->angle_units == angle_units)
635 return;
636 equation->priv->angle_units = angle_units;
637 g_object_notify(G_OBJECT(equation), "angle-units");
641 MPAngleUnit
642 math_equation_get_angle_units(MathEquation *equation)
644 return equation->priv->angle_units;
648 void
649 math_equation_set_source_currency(MathEquation *equation, const gchar *currency)
651 // FIXME: Pick based on locale
652 if (!currency || currency[0] == '\0')
653 currency = currency_info[0].short_name;
655 if (strcmp(equation->priv->source_currency, currency) == 0)
656 return;
657 g_free(equation->priv->source_currency);
658 equation->priv->source_currency = g_strdup(currency);
659 g_object_notify(G_OBJECT(equation), "source-currency");
663 const gchar *
664 math_equation_get_source_currency(MathEquation *equation)
666 return equation->priv->source_currency;
670 void
671 math_equation_set_target_currency(MathEquation *equation, const gchar *currency)
673 // FIXME: Pick based on locale
674 if (!currency || currency[0] == '\0')
675 currency = currency_info[0].short_name;
677 if (strcmp(equation->priv->target_currency, currency) == 0)
678 return;
679 g_free(equation->priv->target_currency);
680 equation->priv->target_currency = g_strdup(currency);
681 g_object_notify(G_OBJECT(equation), "target-currency");
685 const gchar *
686 math_equation_get_target_currency(MathEquation *equation)
688 return equation->priv->target_currency;
692 void
693 math_equation_set_source_units(MathEquation *equation, const gchar *units)
695 if (strcmp(equation->priv->source_units, units) == 0)
696 return;
697 g_free(equation->priv->source_units);
698 equation->priv->source_units = g_strdup(units);
699 g_object_notify(G_OBJECT(equation), "source-units");
702 const gchar *
703 math_equation_get_source_units(MathEquation *equation)
705 return equation->priv->source_units;
709 void
710 math_equation_set_target_units(MathEquation *equation, const gchar *units)
712 if (strcmp(equation->priv->target_units, units) == 0)
713 return;
714 g_free(equation->priv->target_units);
715 equation->priv->target_units = g_strdup(units);
716 g_object_notify(G_OBJECT(equation), "target-units");
720 const gchar *
721 math_equation_get_target_units(MathEquation *equation)
723 return equation->priv->target_units;
727 void
728 math_equation_set_status(MathEquation *equation, const gchar *status)
730 if (strcmp(equation->priv->state.status, status) == 0)
731 return;
733 g_free(equation->priv->state.status);
734 equation->priv->state.status = g_strdup(status);
735 g_object_notify(G_OBJECT(equation), "status");
739 const gchar *
740 math_equation_get_status(MathEquation *equation)
742 return equation->priv->state.status;
746 gboolean
747 math_equation_is_empty(MathEquation *equation)
749 return gtk_text_buffer_get_char_count(GTK_TEXT_BUFFER(equation)) == 0;
753 gboolean
754 math_equation_is_result(MathEquation *equation)
756 char *text;
757 gboolean result;
759 text = math_equation_get_equation(equation);
760 result = strcmp(text, "ans") == 0;
761 g_free(text);
763 return result;
767 gchar *
768 math_equation_get_display(MathEquation *equation)
770 GtkTextIter start, end;
772 gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(equation), &start, &end);
773 return gtk_text_buffer_get_text(GTK_TEXT_BUFFER(equation), &start, &end, FALSE);
777 gchar *
778 math_equation_get_equation(MathEquation *equation)
780 gchar *text;
781 GString *eq_text;
782 gint ans_start = -1, ans_end = -1, offset;
783 const gchar *read_iter;
784 gboolean last_is_digit = FALSE;
786 text = math_equation_get_display(equation);
787 eq_text = g_string_sized_new(strlen(text));
789 if (equation->priv->ans_start)
790 get_ans_offsets(equation, &ans_start, &ans_end);
792 for (read_iter = text, offset = 0; *read_iter != '\0'; read_iter = g_utf8_next_char(read_iter), offset++) {
793 gunichar c;
794 gboolean is_digit, next_is_digit;
796 c = g_utf8_get_char(read_iter);
797 is_digit = g_unichar_isdigit(c);
798 next_is_digit = g_unichar_isdigit(g_utf8_get_char(g_utf8_next_char(read_iter)));
800 /* Replace ans text with variable */
801 if (offset == ans_start) {
802 g_string_append(eq_text, "ans");
803 read_iter = g_utf8_offset_to_pointer(read_iter, ans_end - ans_start - 1);
804 offset += ans_end - ans_start - 1;
805 is_digit = FALSE;
806 continue;
809 /* Ignore thousands separators */
810 if (c == mp_serializer_get_thousands_separator(equation->priv->serializer) && last_is_digit && next_is_digit)
812 /* Substitute radix character */
813 else if (c == mp_serializer_get_radix(equation->priv->serializer) && (last_is_digit || next_is_digit))
814 g_string_append_unichar(eq_text, '.');
815 else
816 g_string_append_unichar(eq_text, c);
818 last_is_digit = is_digit;
820 g_free(text);
822 text = eq_text->str;
823 g_string_free(eq_text, FALSE);
825 return text;
829 gboolean
830 math_equation_get_number(MathEquation *equation, MPNumber *z)
832 gchar *text;
833 gboolean result;
835 text = math_equation_get_equation(equation);
836 result = !mp_serializer_from_string(equation->priv->serializer, text, z);
837 g_free(text);
839 return result;
843 MpSerializer *
844 math_equation_get_serializer(MathEquation *equation)
846 return equation->priv->serializer;
850 void
851 math_equation_set_number_mode(MathEquation *equation, NumberMode mode)
853 if (equation->priv->number_mode == mode)
854 return;
856 equation->priv->can_super_minus = mode == SUPERSCRIPT;
858 equation->priv->number_mode = mode;
859 g_object_notify(G_OBJECT(equation), "number-mode");
863 NumberMode
864 math_equation_get_number_mode(MathEquation *equation)
866 return equation->priv->number_mode;
870 gboolean
871 math_equation_in_solve(MathEquation *equation)
873 return equation->priv->in_solve;
877 const MPNumber *
878 math_equation_get_answer(MathEquation *equation)
880 return &equation->priv->state.ans;
884 void
885 math_equation_store(MathEquation *equation, const gchar *name)
887 MPNumber t;
889 if (!math_equation_get_number(equation, &t))
890 math_equation_set_status(equation, _("No sane value to store"));
891 else
892 math_variables_set(equation->priv->variables, name, &t);
896 void
897 math_equation_recall(MathEquation *equation, const gchar *name)
899 math_equation_insert(equation, name);
903 void
904 math_equation_set(MathEquation *equation, const gchar *text)
907 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), text, -1);
908 clear_ans(equation, FALSE);
912 void
913 math_equation_set_number(MathEquation *equation, const MPNumber *x)
915 char *text;
916 GtkTextIter start, end;
918 /* Show the number in the user chosen format */
919 text = mp_serializer_to_string(equation->priv->serializer, x);
920 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), text, -1);
921 mp_set_from_mp(x, &equation->priv->state.ans);
923 /* Mark this text as the answer variable */
924 gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(equation), &start, &end);
925 clear_ans(equation, FALSE);
926 equation->priv->ans_start = gtk_text_buffer_create_mark(GTK_TEXT_BUFFER(equation), NULL, &start, FALSE);
927 equation->priv->ans_end = gtk_text_buffer_create_mark(GTK_TEXT_BUFFER(equation), NULL, &end, TRUE);
928 gtk_text_buffer_apply_tag(GTK_TEXT_BUFFER(equation), equation->priv->ans_tag, &start, &end);
929 g_free(text);
933 void
934 math_equation_insert(MathEquation *equation, const gchar *text)
936 /* Replace ** with ^ (not on all keyboards) */
937 if (!gtk_text_buffer_get_has_selection(GTK_TEXT_BUFFER(equation)) &&
938 strcmp(text, "×") == 0 && equation->priv->state.entered_multiply) {
939 GtkTextIter iter;
941 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, gtk_text_buffer_get_insert(GTK_TEXT_BUFFER(equation)));
942 gtk_text_buffer_backspace(GTK_TEXT_BUFFER(equation), &iter, TRUE, TRUE);
943 gtk_text_buffer_insert_at_cursor(GTK_TEXT_BUFFER(equation), "^", -1);
944 return;
947 /* Start new equation when entering digits after existing result */
948 if(math_equation_is_result(equation) && g_unichar_isdigit(g_utf8_get_char(text)))
949 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), "", -1);
951 /* Can't enter superscript minus after entering digits */
952 if (strstr("⁰¹²³⁴⁵⁶⁷⁸⁹", text) != NULL || strcmp("⁻", text) == 0)
953 equation->priv->can_super_minus = FALSE;
955 /* Disable super/subscript mode when finished entering */
956 if (strstr("⁻⁰¹²³⁴⁵⁶⁷⁸⁹₀₁₂₃₄₅₆₇₈₉", text) == NULL)
957 math_equation_set_number_mode(equation, NORMAL);
959 gtk_text_buffer_delete_selection(GTK_TEXT_BUFFER(equation), FALSE, FALSE);
960 gtk_text_buffer_insert_at_cursor(GTK_TEXT_BUFFER(equation), text, -1);
964 void
965 math_equation_insert_digit(MathEquation *equation, guint digit)
967 static const char *subscript_digits[] = {"₀", "₁", "₂", "₃", "₄", "₅", "₆", "₇", "₈", "₉", NULL};
968 static const char *superscript_digits[] = {"⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹", NULL};
970 if (equation->priv->number_mode == NORMAL || digit >= 10) {
971 gchar buffer[7];
972 gint len;
973 len = g_unichar_to_utf8(math_equation_get_digit_text(equation, digit), buffer);
974 buffer[len] = '\0';
975 math_equation_insert(equation, buffer);
977 else if (equation->priv->number_mode == SUPERSCRIPT)
978 math_equation_insert(equation, superscript_digits[digit]);
979 else if (equation->priv->number_mode == SUBSCRIPT)
980 math_equation_insert(equation, subscript_digits[digit]);
984 void
985 math_equation_insert_numeric_point(MathEquation *equation)
987 gchar buffer[7];
988 gint len;
989 len = g_unichar_to_utf8(mp_serializer_get_radix(equation->priv->serializer), buffer);
990 buffer[len] = '\0';
991 math_equation_insert(equation, buffer);
995 void
996 math_equation_insert_number(MathEquation *equation, const MPNumber *x)
998 char *text;
999 text = mp_serializer_to_string(equation->priv->serializer, x);
1000 math_equation_insert(equation, text);
1001 g_free(text);
1005 void
1006 math_equation_insert_exponent(MathEquation *equation)
1008 math_equation_insert(equation, "×10");
1009 math_equation_set_number_mode(equation, SUPERSCRIPT);
1013 void
1014 math_equation_insert_subtract(MathEquation *equation)
1016 if (equation->priv->number_mode == SUPERSCRIPT && equation->priv->can_super_minus) {
1017 math_equation_insert(equation, "⁻");
1018 equation->priv->can_super_minus = FALSE;
1020 else {
1021 math_equation_insert(equation, "−");
1022 math_equation_set_number_mode(equation, NORMAL);
1027 static int
1028 variable_is_defined(const char *name, void *data)
1030 MathEquation *equation = data;
1031 char *c, *lower_name;
1033 lower_name = strdup(name);
1034 for (c = lower_name; *c; c++)
1035 *c = tolower(*c);
1037 if (strcmp(lower_name, "rand") == 0 ||
1038 strcmp(lower_name, "ans") == 0) {
1039 g_free(lower_name);
1040 return 1;
1042 g_free(lower_name);
1044 return math_variables_get(equation->priv->variables, name) != NULL;
1048 static int
1049 get_variable(const char *name, MPNumber *z, void *data)
1051 char *c, *lower_name;
1052 int result = 1;
1053 MathEquation *equation = data;
1054 MPNumber *t;
1056 lower_name = strdup(name);
1057 for (c = lower_name; *c; c++)
1058 *c = tolower(*c);
1060 if (strcmp(lower_name, "rand") == 0)
1061 mp_set_from_random(z);
1062 else if (strcmp(lower_name, "ans") == 0)
1063 mp_set_from_mp(&equation->priv->state.ans, z);
1064 else {
1065 t = math_variables_get(equation->priv->variables, name);
1066 if (t)
1067 mp_set_from_mp(t, z);
1068 else
1069 result = 0;
1072 free(lower_name);
1074 return result;
1078 static void
1079 set_variable(const char *name, const MPNumber *x, void *data)
1081 MathEquation *equation = data;
1082 /* FIXME: Don't allow writing to built-in variables, e.g. ans, rand, sin, ... */
1083 math_variables_set(equation->priv->variables, name, x);
1087 static int
1088 convert(const MPNumber *x, const char *x_units, const char *z_units, MPNumber *z, void *data)
1090 MathEquation *equation = data;
1091 return unit_manager_convert(equation->priv->unit_manager, x, x_units, z_units, z);
1095 static int
1096 parse(MathEquation *equation, const char *text, MPNumber *z, char **error_token)
1098 MPEquationOptions options;
1100 memset(&options, 0, sizeof(options));
1101 options.base = mp_serializer_get_base(equation->priv->serializer);
1102 options.wordlen = equation->priv->word_size;
1103 options.angle_units = equation->priv->angle_units;
1104 options.variable_is_defined = variable_is_defined;
1105 options.get_variable = get_variable;
1106 options.set_variable = set_variable;
1107 options.convert = convert;
1108 options.callback_data = equation;
1110 return mp_equation_parse(text, &options, z, error_token);
1115 * Executed in separate thread. It is thus not a good idea to write to anything
1116 * in MathEquation but the async queue from here.
1118 static gpointer
1119 math_equation_solve_real(gpointer data)
1121 MathEquation *equation = MATH_EQUATION(data);
1122 SolveData *solvedata = g_slice_new0(SolveData);
1124 gint n_brackets = 0, result;
1125 gchar *c, *text, *error_token;
1126 GString *equation_text;
1127 MPNumber z;
1129 text = math_equation_get_equation(equation);
1130 equation_text = g_string_new(text);
1131 g_free(text);
1132 /* Count the number of brackets and automatically add missing closing brackets */
1133 for (c = equation_text->str; *c; c++) {
1134 if (*c == '(')
1135 n_brackets++;
1136 else if (*c == ')')
1137 n_brackets--;
1139 while (n_brackets > 0) {
1140 g_string_append_c(equation_text, ')');
1141 n_brackets--;
1145 result = parse(equation, equation_text->str, &z, &error_token);
1146 g_string_free(equation_text, TRUE);
1148 switch (result) {
1149 case PARSER_ERR_NONE:
1150 solvedata->number_result = g_slice_new(MPNumber);
1151 mp_set_from_mp(&z, solvedata->number_result);
1152 break;
1154 case PARSER_ERR_OVERFLOW:
1155 solvedata->error = g_strdup(/* Error displayed to user when they perform a bitwise operation on numbers greater than the current word */
1156 _("Overflow. Try a bigger word size"));
1157 break;
1159 case PARSER_ERR_UNKNOWN_VARIABLE:
1160 solvedata->error = g_strdup_printf(/* Error displayed to user when they an unknown variable is entered */
1161 _("Unknown variable '%s'"), error_token);
1162 break;
1164 case PARSER_ERR_UNKNOWN_FUNCTION:
1165 solvedata->error = g_strdup_printf(/* Error displayed to user when an unknown function is entered */
1166 _("Function '%s' is not defined"), error_token);
1167 break;
1169 case PARSER_ERR_UNKNOWN_CONVERSION:
1170 solvedata->error = g_strdup(/* Error displayed to user when an conversion with unknown units is attempted */
1171 _("Unknown conversion"));
1172 break;
1174 case PARSER_ERR_MP:
1175 solvedata->error = g_strdup(mp_get_error());
1176 break;
1178 default:
1179 solvedata->error = g_strdup(/* Error displayed to user when they enter an invalid calculation */
1180 _("Malformed expression"));
1181 break;
1183 g_async_queue_push(equation->priv->queue, solvedata);
1185 return NULL;
1189 static gboolean
1190 math_equation_show_in_progress(gpointer data)
1192 MathEquation *equation = MATH_EQUATION(data);
1193 if (equation->priv->in_solve)
1194 math_equation_set_status(equation, "Calculating");
1195 return false;
1199 static gboolean
1200 math_equation_look_for_answer(gpointer data)
1202 MathEquation *equation = MATH_EQUATION(data);
1203 SolveData *result = g_async_queue_try_pop(equation->priv->queue);
1205 if (result == NULL)
1206 return true;
1208 equation->priv->in_solve = false;
1210 if (!result->error)
1211 math_equation_set_status(equation, "");
1213 if (result->error != NULL) {
1214 math_equation_set_status(equation, result->error);
1215 g_free(result->error);
1217 else if (result->number_result != NULL) {
1218 math_equation_set_number(equation, result->number_result);
1219 g_slice_free(MPNumber, result->number_result);
1221 else if (result->text_result != NULL) {
1222 math_equation_set(equation, result->text_result);
1223 g_free(result->text_result);
1225 g_slice_free(SolveData, result);
1227 return false;
1231 void
1232 math_equation_solve(MathEquation *equation)
1234 GError *error = NULL;
1236 // FIXME: should replace calculation or give error message
1237 if (equation->priv->in_solve)
1238 return;
1240 if (math_equation_is_empty(equation))
1241 return;
1243 /* If showing a result return to the equation that caused it */
1244 // FIXME: Result may not be here due to solve (i.e. the user may have entered "ans")
1245 if (math_equation_is_result(equation)) {
1246 math_equation_undo(equation);
1247 return;
1250 equation->priv->in_solve = true;
1252 math_equation_set_number_mode(equation, NORMAL);
1254 g_thread_create(math_equation_solve_real, equation, false, &error);
1256 if (error)
1257 g_warning("Error spawning thread for calculations: %s\n", error->message);
1259 g_timeout_add(50, math_equation_look_for_answer, equation);
1260 g_timeout_add(100, math_equation_show_in_progress, equation);
1264 static gpointer
1265 math_equation_factorize_real(gpointer data)
1267 GString *text;
1268 GList *factors, *factor;
1269 MPNumber x;
1270 MathEquation *equation = MATH_EQUATION(data);
1271 SolveData *result = g_slice_new0(SolveData);
1273 math_equation_get_number(equation, &x);
1274 factors = mp_factorize(&x);
1276 text = g_string_new("");
1278 for (factor = factors; factor; factor = factor->next) {
1279 gchar *temp;
1280 MPNumber *n;
1282 n = factor->data;
1283 temp = mp_serializer_to_string(equation->priv->serializer, n);
1284 g_string_append(text, temp);
1285 if (factor->next)
1286 g_string_append(text, "×");
1287 g_slice_free(MPNumber, n);
1288 g_free(temp);
1290 g_list_free(factors);
1292 result->text_result = g_strndup(text->str, text->len);
1293 g_async_queue_push(equation->priv->queue, result);
1294 g_string_free(text, TRUE);
1296 return NULL;
1300 void
1301 math_equation_factorize(MathEquation *equation)
1303 MPNumber x;
1304 GError *error = NULL;
1306 // FIXME: should replace calculation or give error message
1307 if (equation->priv->in_solve)
1308 return;
1310 if (!math_equation_get_number(equation, &x) || !mp_is_integer(&x)) {
1311 /* Error displayed when trying to factorize a non-integer value */
1312 math_equation_set_status(equation, _("Need an integer to factorize"));
1313 return;
1316 equation->priv->in_solve = true;
1318 g_thread_create(math_equation_factorize_real, equation, false, &error);
1320 if (error)
1321 g_warning("Error spawning thread for calculations: %s\n", error->message);
1323 g_timeout_add(50, math_equation_look_for_answer, equation);
1324 g_timeout_add(100, math_equation_show_in_progress, equation);
1328 void
1329 math_equation_delete(MathEquation *equation)
1331 gint cursor;
1332 GtkTextIter start, end;
1334 g_object_get(G_OBJECT(equation), "cursor-position", &cursor, NULL);
1335 if (cursor >= gtk_text_buffer_get_char_count(GTK_TEXT_BUFFER(equation)))
1336 return;
1338 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &start, cursor);
1339 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &end, cursor+1);
1340 gtk_text_buffer_delete(GTK_TEXT_BUFFER(equation), &start, &end);
1344 void
1345 math_equation_backspace(MathEquation *equation)
1347 /* Can't delete empty display */
1348 if (math_equation_is_empty(equation))
1349 return;
1351 if (gtk_text_buffer_get_has_selection(GTK_TEXT_BUFFER(equation)))
1352 gtk_text_buffer_delete_selection(GTK_TEXT_BUFFER(equation), FALSE, FALSE);
1353 else {
1354 GtkTextIter iter;
1355 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, gtk_text_buffer_get_insert(GTK_TEXT_BUFFER(equation)));
1356 gtk_text_buffer_backspace(GTK_TEXT_BUFFER(equation), &iter, TRUE, TRUE);
1361 void
1362 math_equation_clear(MathEquation *equation)
1364 math_equation_set_number_mode(equation, NORMAL);
1365 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), "", -1);
1366 clear_ans(equation, FALSE);
1370 void
1371 math_equation_shift(MathEquation *equation, gint count)
1373 MPNumber z;
1375 if (!math_equation_get_number(equation, &z)) {
1376 math_equation_set_status(equation,
1377 /* This message is displayed in the status bar when a bit
1378 shift operation is performed and the display does not contain a number */
1379 _("No sane value to bitwise shift"));
1380 return;
1383 mp_shift(&z, count, &z);
1384 math_equation_set_number(equation, &z);
1388 void
1389 math_equation_toggle_bit(MathEquation *equation, guint bit)
1391 MPNumber x;
1392 guint64 bits;
1393 gboolean result;
1395 result = math_equation_get_number(equation, &x);
1396 if (result) {
1397 MPNumber max;
1398 mp_set_from_unsigned_integer(G_MAXUINT64, &max);
1399 if (mp_is_negative(&x) || mp_is_greater_than(&x, &max))
1400 result = FALSE;
1401 else
1402 bits = mp_cast_to_unsigned_int(&x);
1405 if (!result) {
1406 math_equation_set_status(equation,
1407 /* Message displayed when cannot toggle bit in display*/
1408 _("Displayed value not an integer"));
1409 return;
1412 bits ^= (1LL << (63 - bit));
1414 mp_set_from_unsigned_integer(bits, &x);
1416 // FIXME: Only do this if in ans format, otherwise set text in same format as previous number
1417 math_equation_set_number(equation, &x);
1421 static void
1422 math_equation_set_property(GObject *object,
1423 guint prop_id,
1424 const GValue *value,
1425 GParamSpec *pspec)
1427 MathEquation *self;
1429 self = MATH_EQUATION(object);
1431 switch (prop_id) {
1432 case PROP_STATUS:
1433 math_equation_set_status(self, g_value_get_string(value));
1434 break;
1435 case PROP_DISPLAY:
1436 math_equation_set(self, g_value_get_string(value));
1437 break;
1438 case PROP_NUMBER_MODE:
1439 math_equation_set_number_mode(self, g_value_get_int(value));
1440 break;
1441 case PROP_ACCURACY:
1442 math_equation_set_accuracy(self, g_value_get_int(value));
1443 break;
1444 case PROP_SHOW_THOUSANDS_SEPARATORS:
1445 math_equation_set_show_thousands_separators(self, g_value_get_boolean(value));
1446 break;
1447 case PROP_SHOW_TRAILING_ZEROES:
1448 math_equation_set_show_trailing_zeroes(self, g_value_get_boolean(value));
1449 break;
1450 case PROP_NUMBER_FORMAT:
1451 math_equation_set_number_format(self, g_value_get_int(value));
1452 break;
1453 case PROP_BASE:
1454 math_equation_set_base(self, g_value_get_int(value));
1455 break;
1456 case PROP_WORD_SIZE:
1457 math_equation_set_word_size(self, g_value_get_int(value));
1458 break;
1459 case PROP_ANGLE_UNITS:
1460 math_equation_set_angle_units(self, g_value_get_int(value));
1461 break;
1462 case PROP_SOURCE_CURRENCY:
1463 math_equation_set_source_currency(self, g_value_get_string(value));
1464 break;
1465 case PROP_TARGET_CURRENCY:
1466 math_equation_set_target_currency(self, g_value_get_string(value));
1467 break;
1468 case PROP_SOURCE_UNITS:
1469 math_equation_set_source_units(self, g_value_get_string(value));
1470 break;
1471 case PROP_TARGET_UNITS:
1472 math_equation_set_target_units(self, g_value_get_string(value));
1473 break;
1474 default:
1475 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
1476 break;
1481 static void
1482 math_equation_get_property(GObject *object,
1483 guint prop_id,
1484 GValue *value,
1485 GParamSpec *pspec)
1487 MathEquation *self;
1488 gchar *text;
1490 self = MATH_EQUATION(object);
1492 switch (prop_id) {
1493 case PROP_STATUS:
1494 g_value_set_string(value, self->priv->state.status);
1495 break;
1496 case PROP_DISPLAY:
1497 text = math_equation_get_display(self);
1498 g_value_set_string(value, text);
1499 g_free(text);
1500 break;
1501 case PROP_EQUATION:
1502 text = math_equation_get_equation(self);
1503 g_value_set_string(value, text);
1504 g_free(text);
1505 break;
1506 case PROP_NUMBER_MODE:
1507 g_value_set_enum(value, self->priv->number_mode);
1508 break;
1509 case PROP_ACCURACY:
1510 g_value_set_int(value, mp_serializer_get_accuracy(self->priv->serializer));
1511 break;
1512 case PROP_SHOW_THOUSANDS_SEPARATORS:
1513 g_value_set_boolean(value, mp_serializer_get_show_thousands_separators(self->priv->serializer));
1514 break;
1515 case PROP_SHOW_TRAILING_ZEROES:
1516 g_value_set_boolean(value, mp_serializer_get_show_trailing_zeroes(self->priv->serializer));
1517 break;
1518 case PROP_NUMBER_FORMAT:
1519 g_value_set_enum(value, mp_serializer_get_number_format(self->priv->serializer));
1520 break;
1521 case PROP_BASE:
1522 g_value_set_int(value, math_equation_get_base(self));
1523 break;
1524 case PROP_WORD_SIZE:
1525 g_value_set_int(value, self->priv->word_size);
1526 break;
1527 case PROP_ANGLE_UNITS:
1528 g_value_set_enum(value, self->priv->angle_units);
1529 break;
1530 case PROP_SOURCE_CURRENCY:
1531 g_value_set_string(value, self->priv->source_currency);
1532 break;
1533 case PROP_TARGET_CURRENCY:
1534 g_value_set_string(value, self->priv->target_currency);
1535 break;
1536 case PROP_SOURCE_UNITS:
1537 g_value_set_string(value, self->priv->source_units);
1538 break;
1539 case PROP_TARGET_UNITS:
1540 g_value_set_string(value, self->priv->target_units);
1541 break;
1542 case PROP_SERIALIZER:
1543 g_value_set_object(value, self->priv->serializer);
1544 break;
1545 default:
1546 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
1547 break;
1552 static void
1553 math_equation_constructed(GObject *object)
1555 GtkTextBuffer *parent_class;
1556 parent_class = g_type_class_peek_parent(MATH_EQUATION_GET_CLASS(object));
1557 if (G_OBJECT_CLASS(parent_class)->constructed)
1558 G_OBJECT_CLASS(parent_class)->constructed(object);
1560 MATH_EQUATION(object)->priv->ans_tag = gtk_text_buffer_create_tag(GTK_TEXT_BUFFER(object), NULL, "weight", PANGO_WEIGHT_BOLD, NULL);
1564 static void
1565 math_equation_class_init(MathEquationClass *klass)
1567 static GEnumValue number_mode_values[] =
1569 {NORMAL, "normal", "normal"},
1570 {SUPERSCRIPT, "superscript", "superscript"},
1571 {SUBSCRIPT, "subscript", "subscript"},
1572 {0, NULL, NULL}
1574 static GEnumValue angle_unit_values[] =
1576 {MP_RADIANS, "radians", "radians"},
1577 {MP_DEGREES, "degrees", "degrees"},
1578 {MP_GRADIANS, "gradians", "gradians"},
1579 {0, NULL, NULL}
1581 GObjectClass *object_class = G_OBJECT_CLASS(klass);
1583 object_class->get_property = math_equation_get_property;
1584 object_class->set_property = math_equation_set_property;
1585 object_class->constructed = math_equation_constructed;
1587 g_type_class_add_private(klass, sizeof(MathEquationPrivate));
1589 number_mode_type = g_enum_register_static("NumberMode", number_mode_values);
1590 number_format_type = math_mp_display_format_get_type();
1591 angle_unit_type = g_enum_register_static("AngleUnit", angle_unit_values);
1593 g_object_class_install_property(object_class,
1594 PROP_STATUS,
1595 g_param_spec_string("status",
1596 "status",
1597 "Equation status",
1599 G_PARAM_READWRITE));
1600 g_object_class_install_property(object_class,
1601 PROP_DISPLAY,
1602 g_param_spec_string("display",
1603 "display",
1604 "Displayed equation text",
1606 G_PARAM_READWRITE));
1607 g_object_class_install_property(object_class,
1608 PROP_EQUATION,
1609 g_param_spec_string("equation",
1610 "equation",
1611 "Equation text",
1613 G_PARAM_READABLE));
1614 g_object_class_install_property(object_class,
1615 PROP_NUMBER_MODE,
1616 g_param_spec_enum("number-mode",
1617 "number-mode",
1618 "Input number mode",
1619 number_mode_type,
1620 NORMAL,
1621 G_PARAM_READWRITE));
1622 g_object_class_install_property(object_class,
1623 PROP_ACCURACY,
1624 g_param_spec_int("accuracy",
1625 "accuracy",
1626 "Display accuracy",
1627 0, 20, 9,
1628 G_PARAM_READWRITE));
1629 g_object_class_install_property(object_class,
1630 PROP_SHOW_THOUSANDS_SEPARATORS,
1631 g_param_spec_boolean("show-thousands-separators",
1632 "show-thousands-separators",
1633 "Show thousands separators",
1634 TRUE,
1635 G_PARAM_READWRITE));
1636 g_object_class_install_property(object_class,
1637 PROP_SHOW_TRAILING_ZEROES,
1638 g_param_spec_boolean("show-trailing-zeroes",
1639 "show-trailing-zeroes",
1640 "Show trailing zeroes",
1641 FALSE,
1642 G_PARAM_READWRITE));
1643 g_object_class_install_property(object_class,
1644 PROP_NUMBER_FORMAT,
1645 g_param_spec_enum("number-format",
1646 "number-format",
1647 "Display format",
1648 number_format_type,
1649 MP_DISPLAY_FORMAT_FIXED,
1650 G_PARAM_READWRITE));
1651 g_object_class_install_property(object_class,
1652 PROP_BASE,
1653 g_param_spec_int("base",
1654 "base",
1655 "Default number base (derived from number-format)",
1656 2, 16, 10,
1657 G_PARAM_READWRITE));
1658 g_object_class_install_property(object_class,
1659 PROP_WORD_SIZE,
1660 g_param_spec_int("word-size",
1661 "word-size",
1662 "Word size in bits",
1663 8, 64, 64,
1664 G_PARAM_READWRITE));
1665 g_object_class_install_property(object_class,
1666 PROP_ANGLE_UNITS,
1667 g_param_spec_enum("angle-units",
1668 "angle-units",
1669 "Angle units",
1670 angle_unit_type,
1671 MP_DEGREES,
1672 G_PARAM_READWRITE));
1673 g_object_class_install_property(object_class,
1674 PROP_SOURCE_CURRENCY,
1675 g_param_spec_string("source-currency",
1676 "source-currency",
1677 "Source Currency",
1679 G_PARAM_READWRITE));
1680 g_object_class_install_property(object_class,
1681 PROP_TARGET_CURRENCY,
1682 g_param_spec_string("target-currency",
1683 "target-currency",
1684 "target Currency",
1686 G_PARAM_READWRITE));
1687 g_object_class_install_property(object_class,
1688 PROP_SOURCE_UNITS,
1689 g_param_spec_string("source-units",
1690 "source-units",
1691 "Source Units",
1693 G_PARAM_READWRITE));
1694 g_object_class_install_property(object_class,
1695 PROP_TARGET_UNITS,
1696 g_param_spec_string("target-units",
1697 "target-units",
1698 "target Units",
1700 G_PARAM_READWRITE));
1701 g_object_class_install_property(object_class,
1702 PROP_SERIALIZER,
1703 g_param_spec_object("serializer",
1704 "serializer",
1705 "Serializer",
1706 MP_TYPE_SERIALIZER,
1707 G_PARAM_READABLE));
1711 static void
1712 pre_insert_text_cb(MathEquation *equation,
1713 GtkTextIter *location,
1714 gchar *text,
1715 gint len,
1716 gpointer user_data)
1718 gunichar c;
1720 if (equation->priv->in_reformat)
1721 return;
1723 /* If following a delete then have already pushed undo stack (GtkTextBuffer
1724 doesn't indicate replace operations so we have to infer them) */
1725 if (!equation->priv->in_delete)
1726 math_equation_push_undo_stack(equation);
1728 /* Clear result on next digit entered if cursor at end of line */
1729 // FIXME Cursor
1730 c = g_utf8_get_char(text);
1731 if ((g_unichar_isdigit(c) || c == mp_serializer_get_radix(equation->priv->serializer)) &&
1732 math_equation_is_result(equation)) {
1733 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), "", -1);
1734 clear_ans(equation, FALSE);
1735 gtk_text_buffer_get_end_iter(GTK_TEXT_BUFFER(equation), location);
1738 if (equation->priv->ans_start) {
1739 gint ans_start, ans_end;
1740 gint offset;
1742 offset = gtk_text_iter_get_offset(location);
1743 get_ans_offsets(equation, &ans_start, &ans_end);
1745 /* Inserted inside ans */
1746 if (offset > ans_start && offset < ans_end)
1747 clear_ans(equation, TRUE);
1752 static gboolean
1753 on_delete(MathEquation *equation)
1755 equation->priv->in_delete = FALSE;
1756 return FALSE;
1760 static void
1761 pre_delete_range_cb(MathEquation *equation,
1762 GtkTextIter *start,
1763 GtkTextIter *end,
1764 gpointer user_data)
1766 if (equation->priv->in_reformat)
1767 return;
1769 math_equation_push_undo_stack(equation);
1771 equation->priv->in_delete = TRUE;
1772 g_idle_add((GSourceFunc)on_delete, equation);
1774 if (equation->priv->ans_start) {
1775 gint ans_start, ans_end;
1776 gint start_offset, end_offset;
1778 start_offset = gtk_text_iter_get_offset(start);
1779 end_offset = gtk_text_iter_get_offset(end);
1780 get_ans_offsets(equation, &ans_start, &ans_end);
1782 /* Deleted part of ans */
1783 if (start_offset < ans_end && end_offset > ans_start)
1784 clear_ans(equation, TRUE);
1789 static void
1790 insert_text_cb(MathEquation *equation,
1791 GtkTextIter *location,
1792 gchar *text,
1793 gint len,
1794 gpointer user_data)
1796 if (equation->priv->in_reformat)
1797 return;
1799 equation->priv->state.entered_multiply = strcmp(text, "×") == 0;
1801 /* Update thousands separators */
1802 reformat_separators(equation);
1804 g_object_notify(G_OBJECT(equation), "display");
1808 static void
1809 delete_range_cb(MathEquation *equation,
1810 GtkTextIter *start,
1811 GtkTextIter *end,
1812 gpointer user_data)
1814 if (equation->priv->in_reformat)
1815 return;
1817 equation->priv->state.entered_multiply = FALSE;
1819 /* Update thousands separators */
1820 reformat_separators(equation);
1822 // FIXME: A replace will emit this both for delete-range and insert-text, can it be avoided?
1823 g_object_notify(G_OBJECT(equation), "display");
1827 static void
1828 math_equation_init(MathEquation *equation)
1830 /* Digits localized for the given language */
1831 const char *digit_values = _("0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F");
1832 const char *default_digits[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"};
1833 gchar **digits;
1834 gboolean use_default_digits = FALSE;
1835 int i;
1837 equation->priv = G_TYPE_INSTANCE_GET_PRIVATE(equation, math_equation_get_type(), MathEquationPrivate);
1839 g_signal_connect(equation, "insert-text", G_CALLBACK(pre_insert_text_cb), equation);
1840 g_signal_connect(equation, "delete-range", G_CALLBACK(pre_delete_range_cb), equation);
1841 g_signal_connect_after(equation, "insert-text", G_CALLBACK(insert_text_cb), equation);
1842 g_signal_connect_after(equation, "delete-range", G_CALLBACK(delete_range_cb), equation);
1844 digits = g_strsplit(digit_values, ",", -1);
1845 for (i = 0; i < 16; i++) {
1846 if (use_default_digits || digits[i] == NULL) {
1847 use_default_digits = TRUE;
1848 equation->priv->digits[i] = g_utf8_get_char(default_digits[i]);
1850 else
1851 equation->priv->digits[i] = g_utf8_get_char(digits[i]);
1853 g_strfreev(digits);
1855 equation->priv->variables = math_variables_new();
1856 equation->priv->unit_manager = unit_manager_get_default();
1858 equation->priv->state.status = g_strdup("");
1859 equation->priv->word_size = 32;
1860 equation->priv->angle_units = MP_DEGREES;
1861 // FIXME: Pick based on locale
1862 equation->priv->source_currency = g_strdup(currency_info[0].short_name);
1863 equation->priv->target_currency = g_strdup(currency_info[0].short_name);
1864 equation->priv->source_units = g_strdup("");
1865 equation->priv->target_units = g_strdup("");
1866 equation->priv->serializer = mp_serializer_new(MP_DISPLAY_FORMAT_AUTOMATIC, 10, 9);
1867 equation->priv->queue = g_async_queue_new();
1869 mp_set_from_integer(0, &equation->priv->state.ans);