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)
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
28 #include "math-equation.h"
31 #include "mp-equation.h"
32 #include "mp-serializer.h"
34 #include "math-enums.h"
44 PROP_SHOW_THOUSANDS_SEPARATORS
,
45 PROP_SHOW_TRAILING_ZEROES
,
57 static GType number_mode_type
, number_format_type
, angle_unit_type
;
59 #define MAX_DIGITS 512
61 /* Expression mode state */
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 */
73 struct MathEquationPrivate
77 gint word_size
; /* Word size in bits */
78 MPAngleUnit angle_units
; /* Units for trigonometric functions */
79 char *source_currency
;
80 char *target_currency
;
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 */
93 gboolean in_undo_operation
;
101 MathVariables
*variables
;
102 UnitManager
*unit_manager
;
103 MpSerializer
*serializer
;
109 MPNumber
*number_result
;
114 G_DEFINE_TYPE (MathEquation
, math_equation
, GTK_TYPE_TEXT_BUFFER
);
120 return g_object_new(math_equation_get_type(), NULL
);
125 math_equation_get_variables(MathEquation
*equation
)
127 return equation
->priv
->variables
;
132 math_equation_get_unit_manager(MathEquation
*equation
)
134 return equation
->priv
->unit_manager
;
139 get_ans_offsets(MathEquation
*equation
, gint
*start
, gint
*end
)
143 if (!equation
->priv
->ans_start
) {
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
);
156 reformat_ans(MathEquation
*equation
)
158 if (!equation
->priv
->ans_start
)
161 gchar
*orig_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) {
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
);
195 count_digits(MathEquation
*equation
, const gchar
*text
)
197 const gchar
*read_iter
;
201 while (*read_iter
!= '\0') {
202 if (!g_unichar_isdigit(g_utf8_get_char(read_iter
)))
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
)))
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
++) {
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
;
251 if (g_unichar_isdigit(c
)) {
253 digit_offset
= count_digits(equation
, read_iter
);
256 /* Expected a thousands separator between these digits - insert it */
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
);
265 gtk_text_buffer_insert(GTK_TEXT_BUFFER(equation
), &iter
, buffer
, -1);
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
);
288 in_number
= in_radix
= FALSE
;
294 equation
->priv
->in_reformat
= FALSE
;
295 equation
->priv
->in_undo_operation
= FALSE
;
300 reformat_display(MathEquation
*equation
, gint old_base
)
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
)
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
);
342 free_state(MathEquationState
*state
)
344 g_free(state
->expression
);
345 g_free(state
->status
);
351 math_equation_push_undo_stack(MathEquation
*equation
)
354 MathEquationState
*state
;
356 if (equation
->priv
->in_undo_operation
)
359 math_equation_set_status(equation
, "");
361 /* Can't redo anymore */
362 for (link
= equation
->priv
->redo_stack
; link
; link
= link
->next
) {
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
);
375 clear_ans(MathEquation
*equation
, gboolean remove_tag
)
377 if (!equation
->priv
->ans_start
)
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
;
396 apply_state(MathEquation
*equation
, MathEquationState
*state
)
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
;
429 math_equation_copy(MathEquation
*equation
)
431 GtkTextIter start
, end
;
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);
444 on_paste(GtkClipboard
*clipboard
, const gchar
*text
, gpointer data
)
446 MathEquation
*equation
= data
;
448 math_equation_insert(equation
, text
);
453 math_equation_paste(MathEquation
*equation
)
455 gtk_clipboard_request_text(gtk_clipboard_get(GDK_NONE
), on_paste
, equation
);
460 math_equation_undo(MathEquation
*equation
)
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"));
472 link
= equation
->priv
->undo_stack
;
473 equation
->priv
->undo_stack
= g_list_remove_link(equation
->priv
->undo_stack
, link
);
477 equation
->priv
->redo_stack
= g_list_prepend(equation
->priv
->redo_stack
, get_current_state(equation
));
479 apply_state(equation
, state
);
485 math_equation_redo(MathEquation
*equation
)
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"));
497 link
= equation
->priv
->redo_stack
;
498 equation
->priv
->redo_stack
= g_list_remove_link(equation
->priv
->redo_stack
, link
);
502 equation
->priv
->undo_stack
= g_list_prepend(equation
->priv
->undo_stack
, get_current_state(equation
));
504 apply_state(equation
, state
);
510 math_equation_get_digit_text(MathEquation
*equation
, guint digit
)
512 return equation
->priv
->digits
[digit
];
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
)
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");
529 math_equation_get_accuracy(MathEquation
*equation
)
531 return mp_serializer_get_accuracy(equation
->priv
->serializer
);
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
)
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");
548 math_equation_get_show_thousands_separators(MathEquation
*equation
)
550 return mp_serializer_get_show_thousands_separators(equation
->priv
->serializer
);
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
)
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");
567 math_equation_get_show_trailing_zeroes(MathEquation
*equation
)
569 return mp_serializer_get_show_trailing_zeroes(equation
->priv
->serializer
);
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
)
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");
587 math_equation_get_number_format(MathEquation
*equation
)
589 return mp_serializer_get_number_format(equation
->priv
->serializer
);
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
)
601 mp_serializer_set_base(equation
->priv
->serializer
, base
);
602 reformat_display(equation
, old_base
);
603 g_object_notify(G_OBJECT(equation
), "base");
608 math_equation_get_base(MathEquation
*equation
)
610 return mp_serializer_get_base(equation
->priv
->serializer
);
615 math_equation_set_word_size(MathEquation
*equation
, gint word_size
)
617 if (equation
->priv
->word_size
== word_size
)
619 equation
->priv
->word_size
= word_size
;
620 g_object_notify(G_OBJECT(equation
), "word-size");
625 math_equation_get_word_size(MathEquation
*equation
)
627 return equation
->priv
->word_size
;
632 math_equation_set_angle_units(MathEquation
*equation
, MPAngleUnit angle_units
)
634 if (equation
->priv
->angle_units
== angle_units
)
636 equation
->priv
->angle_units
= angle_units
;
637 g_object_notify(G_OBJECT(equation
), "angle-units");
642 math_equation_get_angle_units(MathEquation
*equation
)
644 return equation
->priv
->angle_units
;
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)
657 g_free(equation
->priv
->source_currency
);
658 equation
->priv
->source_currency
= g_strdup(currency
);
659 g_object_notify(G_OBJECT(equation
), "source-currency");
664 math_equation_get_source_currency(MathEquation
*equation
)
666 return equation
->priv
->source_currency
;
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)
679 g_free(equation
->priv
->target_currency
);
680 equation
->priv
->target_currency
= g_strdup(currency
);
681 g_object_notify(G_OBJECT(equation
), "target-currency");
686 math_equation_get_target_currency(MathEquation
*equation
)
688 return equation
->priv
->target_currency
;
693 math_equation_set_source_units(MathEquation
*equation
, const gchar
*units
)
695 if (strcmp(equation
->priv
->source_units
, units
) == 0)
697 g_free(equation
->priv
->source_units
);
698 equation
->priv
->source_units
= g_strdup(units
);
699 g_object_notify(G_OBJECT(equation
), "source-units");
703 math_equation_get_source_units(MathEquation
*equation
)
705 return equation
->priv
->source_units
;
710 math_equation_set_target_units(MathEquation
*equation
, const gchar
*units
)
712 if (strcmp(equation
->priv
->target_units
, units
) == 0)
714 g_free(equation
->priv
->target_units
);
715 equation
->priv
->target_units
= g_strdup(units
);
716 g_object_notify(G_OBJECT(equation
), "target-units");
721 math_equation_get_target_units(MathEquation
*equation
)
723 return equation
->priv
->target_units
;
728 math_equation_set_status(MathEquation
*equation
, const gchar
*status
)
730 if (strcmp(equation
->priv
->state
.status
, status
) == 0)
733 g_free(equation
->priv
->state
.status
);
734 equation
->priv
->state
.status
= g_strdup(status
);
735 g_object_notify(G_OBJECT(equation
), "status");
740 math_equation_get_status(MathEquation
*equation
)
742 return equation
->priv
->state
.status
;
747 math_equation_is_empty(MathEquation
*equation
)
749 return gtk_text_buffer_get_char_count(GTK_TEXT_BUFFER(equation
)) == 0;
754 math_equation_is_result(MathEquation
*equation
)
759 text
= math_equation_get_equation(equation
);
760 result
= strcmp(text
, "ans") == 0;
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
);
778 math_equation_get_equation(MathEquation
*equation
)
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
++) {
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;
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
, '.');
816 g_string_append_unichar(eq_text
, c
);
818 last_is_digit
= is_digit
;
823 g_string_free(eq_text
, FALSE
);
830 math_equation_get_number(MathEquation
*equation
, MPNumber
*z
)
835 text
= math_equation_get_equation(equation
);
836 result
= !mp_serializer_from_string(equation
->priv
->serializer
, text
, z
);
844 math_equation_get_serializer(MathEquation
*equation
)
846 return equation
->priv
->serializer
;
851 math_equation_set_number_mode(MathEquation
*equation
, NumberMode mode
)
853 if (equation
->priv
->number_mode
== mode
)
856 equation
->priv
->can_super_minus
= mode
== SUPERSCRIPT
;
858 equation
->priv
->number_mode
= mode
;
859 g_object_notify(G_OBJECT(equation
), "number-mode");
864 math_equation_get_number_mode(MathEquation
*equation
)
866 return equation
->priv
->number_mode
;
871 math_equation_in_solve(MathEquation
*equation
)
873 return equation
->priv
->in_solve
;
878 math_equation_get_answer(MathEquation
*equation
)
880 return &equation
->priv
->state
.ans
;
885 math_equation_store(MathEquation
*equation
, const gchar
*name
)
889 if (!math_equation_get_number(equation
, &t
))
890 math_equation_set_status(equation
, _("No sane value to store"));
892 math_variables_set(equation
->priv
->variables
, name
, &t
);
897 math_equation_recall(MathEquation
*equation
, const gchar
*name
)
899 math_equation_insert(equation
, name
);
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
);
913 math_equation_set_number(MathEquation
*equation
, const MPNumber
*x
)
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
);
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
) {
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);
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);
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) {
973 len
= g_unichar_to_utf8(math_equation_get_digit_text(equation
, digit
), buffer
);
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
]);
985 math_equation_insert_numeric_point(MathEquation
*equation
)
989 len
= g_unichar_to_utf8(mp_serializer_get_radix(equation
->priv
->serializer
), buffer
);
991 math_equation_insert(equation
, buffer
);
996 math_equation_insert_number(MathEquation
*equation
, const MPNumber
*x
)
999 text
= mp_serializer_to_string(equation
->priv
->serializer
, x
);
1000 math_equation_insert(equation
, text
);
1006 math_equation_insert_exponent(MathEquation
*equation
)
1008 math_equation_insert(equation
, "×10");
1009 math_equation_set_number_mode(equation
, SUPERSCRIPT
);
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
;
1021 math_equation_insert(equation
, "−");
1022 math_equation_set_number_mode(equation
, NORMAL
);
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
++)
1037 if (strcmp(lower_name
, "rand") == 0 ||
1038 strcmp(lower_name
, "ans") == 0) {
1044 return math_variables_get(equation
->priv
->variables
, name
) != NULL
;
1049 get_variable(const char *name
, MPNumber
*z
, void *data
)
1051 char *c
, *lower_name
;
1053 MathEquation
*equation
= data
;
1056 lower_name
= strdup(name
);
1057 for (c
= lower_name
; *c
; 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
);
1065 t
= math_variables_get(equation
->priv
->variables
, name
);
1067 mp_set_from_mp(t
, z
);
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
);
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
);
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.
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
;
1129 text
= math_equation_get_equation(equation
);
1130 equation_text
= g_string_new(text
);
1132 /* Count the number of brackets and automatically add missing closing brackets */
1133 for (c
= equation_text
->str
; *c
; c
++) {
1139 while (n_brackets
> 0) {
1140 g_string_append_c(equation_text
, ')');
1145 result
= parse(equation
, equation_text
->str
, &z
, &error_token
);
1146 g_string_free(equation_text
, TRUE
);
1149 case PARSER_ERR_NONE
:
1150 solvedata
->number_result
= g_slice_new(MPNumber
);
1151 mp_set_from_mp(&z
, solvedata
->number_result
);
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"));
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
);
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
);
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"));
1175 solvedata
->error
= g_strdup(mp_get_error());
1179 solvedata
->error
= g_strdup(/* Error displayed to user when they enter an invalid calculation */
1180 _("Malformed expression"));
1183 g_async_queue_push(equation
->priv
->queue
, solvedata
);
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");
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
);
1208 equation
->priv
->in_solve
= false;
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
);
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
)
1240 if (math_equation_is_empty(equation
))
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
);
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
);
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
);
1265 math_equation_factorize_real(gpointer data
)
1268 GList
*factors
, *factor
;
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
) {
1283 temp
= mp_serializer_to_string(equation
->priv
->serializer
, n
);
1284 g_string_append(text
, temp
);
1286 g_string_append(text
, "×");
1287 g_slice_free(MPNumber
, n
);
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
);
1301 math_equation_factorize(MathEquation
*equation
)
1304 GError
*error
= NULL
;
1306 // FIXME: should replace calculation or give error message
1307 if (equation
->priv
->in_solve
)
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"));
1316 equation
->priv
->in_solve
= true;
1318 g_thread_create(math_equation_factorize_real
, equation
, false, &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
);
1329 math_equation_delete(MathEquation
*equation
)
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
)))
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
);
1345 math_equation_backspace(MathEquation
*equation
)
1347 /* Can't delete empty display */
1348 if (math_equation_is_empty(equation
))
1351 if (gtk_text_buffer_get_has_selection(GTK_TEXT_BUFFER(equation
)))
1352 gtk_text_buffer_delete_selection(GTK_TEXT_BUFFER(equation
), FALSE
, FALSE
);
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
);
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
);
1371 math_equation_shift(MathEquation
*equation
, gint count
)
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"));
1383 mp_shift(&z
, count
, &z
);
1384 math_equation_set_number(equation
, &z
);
1389 math_equation_toggle_bit(MathEquation
*equation
, guint bit
)
1395 result
= math_equation_get_number(equation
, &x
);
1398 mp_set_from_unsigned_integer(G_MAXUINT64
, &max
);
1399 if (mp_is_negative(&x
) || mp_is_greater_than(&x
, &max
))
1402 bits
= mp_cast_to_unsigned_int(&x
);
1406 math_equation_set_status(equation
,
1407 /* Message displayed when cannot toggle bit in display*/
1408 _("Displayed value not an integer"));
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
);
1422 math_equation_set_property(GObject
*object
,
1424 const GValue
*value
,
1429 self
= MATH_EQUATION(object
);
1433 math_equation_set_status(self
, g_value_get_string(value
));
1436 math_equation_set(self
, g_value_get_string(value
));
1438 case PROP_NUMBER_MODE
:
1439 math_equation_set_number_mode(self
, g_value_get_int(value
));
1442 math_equation_set_accuracy(self
, g_value_get_int(value
));
1444 case PROP_SHOW_THOUSANDS_SEPARATORS
:
1445 math_equation_set_show_thousands_separators(self
, g_value_get_boolean(value
));
1447 case PROP_SHOW_TRAILING_ZEROES
:
1448 math_equation_set_show_trailing_zeroes(self
, g_value_get_boolean(value
));
1450 case PROP_NUMBER_FORMAT
:
1451 math_equation_set_number_format(self
, g_value_get_int(value
));
1454 math_equation_set_base(self
, g_value_get_int(value
));
1456 case PROP_WORD_SIZE
:
1457 math_equation_set_word_size(self
, g_value_get_int(value
));
1459 case PROP_ANGLE_UNITS
:
1460 math_equation_set_angle_units(self
, g_value_get_int(value
));
1462 case PROP_SOURCE_CURRENCY
:
1463 math_equation_set_source_currency(self
, g_value_get_string(value
));
1465 case PROP_TARGET_CURRENCY
:
1466 math_equation_set_target_currency(self
, g_value_get_string(value
));
1468 case PROP_SOURCE_UNITS
:
1469 math_equation_set_source_units(self
, g_value_get_string(value
));
1471 case PROP_TARGET_UNITS
:
1472 math_equation_set_target_units(self
, g_value_get_string(value
));
1475 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
1482 math_equation_get_property(GObject
*object
,
1490 self
= MATH_EQUATION(object
);
1494 g_value_set_string(value
, self
->priv
->state
.status
);
1497 text
= math_equation_get_display(self
);
1498 g_value_set_string(value
, text
);
1502 text
= math_equation_get_equation(self
);
1503 g_value_set_string(value
, text
);
1506 case PROP_NUMBER_MODE
:
1507 g_value_set_enum(value
, self
->priv
->number_mode
);
1510 g_value_set_int(value
, mp_serializer_get_accuracy(self
->priv
->serializer
));
1512 case PROP_SHOW_THOUSANDS_SEPARATORS
:
1513 g_value_set_boolean(value
, mp_serializer_get_show_thousands_separators(self
->priv
->serializer
));
1515 case PROP_SHOW_TRAILING_ZEROES
:
1516 g_value_set_boolean(value
, mp_serializer_get_show_trailing_zeroes(self
->priv
->serializer
));
1518 case PROP_NUMBER_FORMAT
:
1519 g_value_set_enum(value
, mp_serializer_get_number_format(self
->priv
->serializer
));
1522 g_value_set_int(value
, math_equation_get_base(self
));
1524 case PROP_WORD_SIZE
:
1525 g_value_set_int(value
, self
->priv
->word_size
);
1527 case PROP_ANGLE_UNITS
:
1528 g_value_set_enum(value
, self
->priv
->angle_units
);
1530 case PROP_SOURCE_CURRENCY
:
1531 g_value_set_string(value
, self
->priv
->source_currency
);
1533 case PROP_TARGET_CURRENCY
:
1534 g_value_set_string(value
, self
->priv
->target_currency
);
1536 case PROP_SOURCE_UNITS
:
1537 g_value_set_string(value
, self
->priv
->source_units
);
1539 case PROP_TARGET_UNITS
:
1540 g_value_set_string(value
, self
->priv
->target_units
);
1542 case PROP_SERIALIZER
:
1543 g_value_set_object(value
, self
->priv
->serializer
);
1546 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
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
);
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"},
1574 static GEnumValue angle_unit_values
[] =
1576 {MP_RADIANS
, "radians", "radians"},
1577 {MP_DEGREES
, "degrees", "degrees"},
1578 {MP_GRADIANS
, "gradians", "gradians"},
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
,
1595 g_param_spec_string("status",
1599 G_PARAM_READWRITE
));
1600 g_object_class_install_property(object_class
,
1602 g_param_spec_string("display",
1604 "Displayed equation text",
1606 G_PARAM_READWRITE
));
1607 g_object_class_install_property(object_class
,
1609 g_param_spec_string("equation",
1614 g_object_class_install_property(object_class
,
1616 g_param_spec_enum("number-mode",
1618 "Input number mode",
1621 G_PARAM_READWRITE
));
1622 g_object_class_install_property(object_class
,
1624 g_param_spec_int("accuracy",
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",
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",
1642 G_PARAM_READWRITE
));
1643 g_object_class_install_property(object_class
,
1645 g_param_spec_enum("number-format",
1649 MP_DISPLAY_FORMAT_FIXED
,
1650 G_PARAM_READWRITE
));
1651 g_object_class_install_property(object_class
,
1653 g_param_spec_int("base",
1655 "Default number base (derived from number-format)",
1657 G_PARAM_READWRITE
));
1658 g_object_class_install_property(object_class
,
1660 g_param_spec_int("word-size",
1662 "Word size in bits",
1664 G_PARAM_READWRITE
));
1665 g_object_class_install_property(object_class
,
1667 g_param_spec_enum("angle-units",
1672 G_PARAM_READWRITE
));
1673 g_object_class_install_property(object_class
,
1674 PROP_SOURCE_CURRENCY
,
1675 g_param_spec_string("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",
1686 G_PARAM_READWRITE
));
1687 g_object_class_install_property(object_class
,
1689 g_param_spec_string("source-units",
1693 G_PARAM_READWRITE
));
1694 g_object_class_install_property(object_class
,
1696 g_param_spec_string("target-units",
1700 G_PARAM_READWRITE
));
1701 g_object_class_install_property(object_class
,
1703 g_param_spec_object("serializer",
1712 pre_insert_text_cb(MathEquation
*equation
,
1713 GtkTextIter
*location
,
1720 if (equation
->priv
->in_reformat
)
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 */
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
;
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
);
1753 on_delete(MathEquation
*equation
)
1755 equation
->priv
->in_delete
= FALSE
;
1761 pre_delete_range_cb(MathEquation
*equation
,
1766 if (equation
->priv
->in_reformat
)
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
);
1790 insert_text_cb(MathEquation
*equation
,
1791 GtkTextIter
*location
,
1796 if (equation
->priv
->in_reformat
)
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");
1809 delete_range_cb(MathEquation
*equation
,
1814 if (equation
->priv
->in_reformat
)
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");
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"};
1834 gboolean use_default_digits
= FALSE
;
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
]);
1851 equation
->priv
->digits
[i
] = g_utf8_get_char(digits
[i
]);
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
);