2 * Copyright (C) 1987-2008 Sun Microsystems, Inc. All Rights Reserved.
3 * Copyright (C) 2008-2011 Robert Ancell
5 * This program is free software: you can redistribute it and/or modify it under
6 * the terms of the GNU General Public License as published by the Free Software
7 * Foundation, either version 2 of the License, or (at your option) any later
8 * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
19 #include <glib/gi18n.h>
21 #include "math-equation.h"
24 #include "mp-equation.h"
25 #include "mp-serializer.h"
27 #include "unit-manager.h"
37 PROP_SHOW_THOUSANDS_SEPARATORS
,
38 PROP_SHOW_TRAILING_ZEROES
,
50 static GType number_mode_type
, number_format_type
, angle_unit_type
;
52 #define MAX_DIGITS 512
54 /* Expression mode state */
56 MPNumber ans
; /* Previously calculated answer */
57 gchar
*expression
; /* Expression entered by user */
58 gint ans_start
, ans_end
; /* Start and end characters for ans variable in expression */
59 gint cursor
; /* ??? */
60 NumberMode number_mode
; /* ??? */
61 gboolean can_super_minus
; /* TRUE if entering minus can generate a superscript minus */
62 gboolean entered_multiply
; /* Last insert was a multiply character */
63 gchar
*status
; /* Equation status */
66 struct MathEquationPrivate
70 gint word_size
; /* Word size in bits */
71 MPAngleUnit angle_units
; /* Units for trigonometric functions */
72 char *source_currency
;
73 char *target_currency
;
76 NumberMode number_mode
; /* ??? */
77 gboolean can_super_minus
; /* TRUE if entering minus can generate a superscript minus */
79 gunichar digits
[16]; /* Localized digits */
81 GtkTextMark
*ans_start
, *ans_end
;
83 MathEquationState state
; /* Equation state */
84 GList
*undo_stack
; /* History of expression mode states */
86 gboolean in_undo_operation
;
94 MathVariables
*variables
;
95 MpSerializer
*serializer
;
101 MPNumber
*number_result
;
106 G_DEFINE_TYPE (MathEquation
, math_equation
, GTK_TYPE_TEXT_BUFFER
);
112 return g_object_new(math_equation_get_type(), NULL
);
117 math_equation_get_variables(MathEquation
*equation
)
119 return equation
->priv
->variables
;
124 get_ans_offsets(MathEquation
*equation
, gint
*start
, gint
*end
)
128 if (!equation
->priv
->ans_start
) {
133 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation
), &iter
, equation
->priv
->ans_start
);
134 *start
= gtk_text_iter_get_offset(&iter
);
135 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation
), &iter
, equation
->priv
->ans_end
);
136 *end
= gtk_text_iter_get_offset(&iter
);
141 reformat_ans(MathEquation
*equation
)
143 if (!equation
->priv
->ans_start
)
146 gchar
*orig_ans_text
;
148 GtkTextIter ans_start
, ans_end
;
150 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation
), &ans_start
, equation
->priv
->ans_start
);
151 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation
), &ans_end
, equation
->priv
->ans_end
);
152 orig_ans_text
= gtk_text_buffer_get_text(GTK_TEXT_BUFFER(equation
), &ans_start
, &ans_end
, FALSE
);
153 ans_text
= mp_serializer_to_string(equation
->priv
->serializer
, &equation
->priv
->state
.ans
);
154 if (strcmp(orig_ans_text
, ans_text
) != 0) {
157 equation
->priv
->in_undo_operation
= TRUE
;
158 equation
->priv
->in_reformat
= TRUE
;
160 start
= gtk_text_iter_get_offset(&ans_start
);
161 gtk_text_buffer_delete(GTK_TEXT_BUFFER(equation
), &ans_start
, &ans_end
);
162 gtk_text_buffer_insert_with_tags(GTK_TEXT_BUFFER(equation
), &ans_end
, ans_text
, -1, equation
->priv
->ans_tag
, NULL
);
164 /* There seems to be a bug in the marks as they alternate being the correct and incorrect ways. Reset them */
165 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation
), &ans_start
, start
);
166 gtk_text_buffer_move_mark(GTK_TEXT_BUFFER(equation
), equation
->priv
->ans_start
, &ans_start
);
167 gtk_text_buffer_move_mark(GTK_TEXT_BUFFER(equation
), equation
->priv
->ans_end
, &ans_end
);
169 equation
->priv
->in_reformat
= FALSE
;
170 equation
->priv
->in_undo_operation
= FALSE
;
172 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation
), &ans_start
, equation
->priv
->ans_start
);
173 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation
), &ans_end
, equation
->priv
->ans_end
);
174 g_free(orig_ans_text
);
180 count_digits(MathEquation
*equation
, const gchar
*text
)
182 const gchar
*read_iter
;
186 while (*read_iter
!= '\0') {
187 if (!g_unichar_isdigit(g_utf8_get_char(read_iter
)))
190 read_iter
= g_utf8_next_char(read_iter
);
192 /* Allow a thousands separator between digits follow a digit */
193 if (g_utf8_get_char(read_iter
) == mp_serializer_get_thousands_separator(equation
->priv
->serializer
)) {
194 read_iter
= g_utf8_next_char(read_iter
);
195 if (!g_unichar_isdigit(g_utf8_get_char(read_iter
)))
207 reformat_separators(MathEquation
*equation
)
209 gchar
*text
, *read_iter
;
210 gint ans_start
, ans_end
;
211 gint offset
, digit_offset
= 0;
212 gboolean in_number
= FALSE
, in_radix
= FALSE
, last_is_tsep
= FALSE
;
214 equation
->priv
->in_undo_operation
= TRUE
;
215 equation
->priv
->in_reformat
= TRUE
;
217 text
= math_equation_get_display(equation
);
218 get_ans_offsets(equation
, &ans_start
, &ans_end
);
219 for (read_iter
= text
, offset
= 0; *read_iter
!= '\0'; read_iter
= g_utf8_next_char(read_iter
), offset
++) {
221 gboolean expect_tsep
;
223 /* See what digit this character is */
224 c
= g_utf8_get_char(read_iter
);
226 expect_tsep
= mp_serializer_get_show_thousands_separators(equation
->priv
->serializer
) &&
227 in_number
&& !in_radix
&& !last_is_tsep
&&
228 digit_offset
> 0 && digit_offset
% mp_serializer_get_thousands_separator_count(equation
->priv
->serializer
) == 0;
229 last_is_tsep
= FALSE
;
231 /* Don't mess with ans */
232 if (offset
>= ans_start
&& offset
<= ans_end
) {
233 in_number
= in_radix
= FALSE
;
236 if (g_unichar_isdigit(c
)) {
238 digit_offset
= count_digits(equation
, read_iter
);
241 /* Expected a thousands separator between these digits - insert it */
247 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation
), &iter
, offset
);
248 len
= g_unichar_to_utf8(mp_serializer_get_thousands_separator(equation
->priv
->serializer
), buffer
);
250 gtk_text_buffer_insert(GTK_TEXT_BUFFER(equation
), &iter
, buffer
, -1);
257 else if (c
== mp_serializer_get_radix(equation
->priv
->serializer
)) {
258 in_number
= in_radix
= TRUE
;
260 else if (c
== mp_serializer_get_thousands_separator(equation
->priv
->serializer
)) {
261 /* Didn't expect thousands separator - delete it */
262 if (!expect_tsep
&& in_number
) {
263 GtkTextIter start
, end
;
264 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation
), &start
, offset
);
265 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation
), &end
, offset
+ 1);
266 gtk_text_buffer_delete(GTK_TEXT_BUFFER(equation
), &start
, &end
);
273 in_number
= in_radix
= FALSE
;
279 equation
->priv
->in_reformat
= FALSE
;
280 equation
->priv
->in_undo_operation
= FALSE
;
285 reformat_display(MathEquation
*equation
)
288 reformat_ans(equation
);
290 /* Add/remove thousands separators */
291 reformat_separators(equation
);
295 static MathEquationState
*
296 get_current_state(MathEquation
*equation
)
298 MathEquationState
*state
;
299 gint ans_start
= -1, ans_end
= -1;
301 state
= g_malloc0(sizeof(MathEquationState
));
303 if (equation
->priv
->ans_start
)
306 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation
), &iter
, equation
->priv
->ans_start
);
307 ans_start
= gtk_text_iter_get_offset(&iter
);
308 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation
), &iter
, equation
->priv
->ans_end
);
309 ans_end
= gtk_text_iter_get_offset(&iter
);
312 mp_set_from_mp(&equation
->priv
->state
.ans
, &state
->ans
);
313 state
->expression
= math_equation_get_display(equation
);
314 state
->ans_start
= ans_start
;
315 state
->ans_end
= ans_end
;
316 g_object_get(G_OBJECT(equation
), "cursor-position", &state
->cursor
, NULL
);
317 state
->number_mode
= equation
->priv
->number_mode
;
318 state
->can_super_minus
= equation
->priv
->can_super_minus
;
319 state
->entered_multiply
= equation
->priv
->state
.entered_multiply
;
320 state
->status
= g_strdup(equation
->priv
->state
.status
);
327 free_state(MathEquationState
*state
)
329 g_free(state
->expression
);
330 g_free(state
->status
);
336 math_equation_push_undo_stack(MathEquation
*equation
)
339 MathEquationState
*state
;
341 if (equation
->priv
->in_undo_operation
)
344 math_equation_set_status(equation
, "");
346 /* Can't redo anymore */
347 for (link
= equation
->priv
->redo_stack
; link
; link
= link
->next
) {
351 g_list_free(equation
->priv
->redo_stack
);
352 equation
->priv
->redo_stack
= NULL
;
354 state
= get_current_state(equation
);
355 equation
->priv
->undo_stack
= g_list_prepend(equation
->priv
->undo_stack
, state
);
360 clear_ans(MathEquation
*equation
, gboolean remove_tag
)
362 if (!equation
->priv
->ans_start
)
366 GtkTextIter start
, end
;
368 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation
), &start
, equation
->priv
->ans_start
);
369 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation
), &end
, equation
->priv
->ans_end
);
370 gtk_text_buffer_remove_tag(GTK_TEXT_BUFFER(equation
), equation
->priv
->ans_tag
, &start
, &end
);
373 gtk_text_buffer_delete_mark(GTK_TEXT_BUFFER(equation
), equation
->priv
->ans_start
);
374 gtk_text_buffer_delete_mark(GTK_TEXT_BUFFER(equation
), equation
->priv
->ans_end
);
375 equation
->priv
->ans_start
= NULL
;
376 equation
->priv
->ans_end
= NULL
;
381 apply_state(MathEquation
*equation
, MathEquationState
*state
)
385 /* Disable undo detection */
386 equation
->priv
->in_undo_operation
= TRUE
;
388 mp_set_from_mp(&state
->ans
, &equation
->priv
->state
.ans
);
390 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation
), state
->expression
, -1);
391 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation
), &cursor
, state
->cursor
);
392 gtk_text_buffer_place_cursor(GTK_TEXT_BUFFER(equation
), &cursor
);
393 clear_ans(equation
, FALSE
);
394 if (state
->ans_start
>= 0) {
395 GtkTextIter start
, end
;
397 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation
), &start
, state
->ans_start
);
398 equation
->priv
->ans_start
= gtk_text_buffer_create_mark(GTK_TEXT_BUFFER(equation
), NULL
, &start
, FALSE
);
399 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation
), &end
, state
->ans_end
);
400 equation
->priv
->ans_end
= gtk_text_buffer_create_mark(GTK_TEXT_BUFFER(equation
), NULL
, &end
, TRUE
);
401 gtk_text_buffer_apply_tag(GTK_TEXT_BUFFER(equation
), equation
->priv
->ans_tag
, &start
, &end
);
404 math_equation_set_number_mode(equation
, state
->number_mode
);
405 equation
->priv
->can_super_minus
= state
->can_super_minus
;
406 equation
->priv
->state
.entered_multiply
= state
->entered_multiply
;
407 math_equation_set_status(equation
, state
->status
);
409 equation
->priv
->in_undo_operation
= FALSE
;
414 math_equation_copy(MathEquation
*equation
)
416 GtkTextIter start
, end
;
419 g_return_if_fail(equation
!= NULL
);
421 if (!gtk_text_buffer_get_selection_bounds(GTK_TEXT_BUFFER(equation
), &start
, &end
))
422 gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(equation
), &start
, &end
);
424 text
= gtk_text_buffer_get_text(GTK_TEXT_BUFFER(equation
), &start
, &end
, FALSE
);
425 gtk_clipboard_set_text(gtk_clipboard_get(GDK_NONE
), text
, -1);
431 on_paste(GtkClipboard
*clipboard
, const gchar
*text
, gpointer data
)
433 MathEquation
*equation
= data
;
435 math_equation_insert(equation
, text
);
440 math_equation_paste(MathEquation
*equation
)
442 g_return_if_fail(equation
!= NULL
);
443 gtk_clipboard_request_text(gtk_clipboard_get(GDK_NONE
), on_paste
, equation
);
448 math_equation_undo(MathEquation
*equation
)
451 MathEquationState
*state
;
453 g_return_if_fail(equation
!= NULL
);
455 if (!equation
->priv
->undo_stack
) {
456 math_equation_set_status(equation
,
457 /* Error shown when trying to undo with no undo history */
458 _("No undo history"));
462 link
= equation
->priv
->undo_stack
;
463 equation
->priv
->undo_stack
= g_list_remove_link(equation
->priv
->undo_stack
, link
);
467 equation
->priv
->redo_stack
= g_list_prepend(equation
->priv
->redo_stack
, get_current_state(equation
));
469 apply_state(equation
, state
);
475 math_equation_redo(MathEquation
*equation
)
478 MathEquationState
*state
;
480 g_return_if_fail(equation
!= NULL
);
482 if (!equation
->priv
->redo_stack
) {
483 math_equation_set_status(equation
,
484 /* Error shown when trying to redo with no redo history */
485 _("No redo history"));
489 link
= equation
->priv
->redo_stack
;
490 equation
->priv
->redo_stack
= g_list_remove_link(equation
->priv
->redo_stack
, link
);
494 equation
->priv
->undo_stack
= g_list_prepend(equation
->priv
->undo_stack
, get_current_state(equation
));
496 apply_state(equation
, state
);
502 math_equation_get_digit_text(MathEquation
*equation
, guint digit
)
504 g_return_val_if_fail(equation
!= NULL
, '?');
505 g_return_val_if_fail(digit
< 16, '?');
507 return equation
->priv
->digits
[digit
];
512 math_equation_set_accuracy(MathEquation
*equation
, gint accuracy
)
514 g_return_if_fail(equation
!= NULL
);
516 if (mp_serializer_get_trailing_digits(equation
->priv
->serializer
) == accuracy
)
518 mp_serializer_set_trailing_digits(equation
->priv
->serializer
, accuracy
);
519 reformat_display(equation
);
520 g_object_notify(G_OBJECT(equation
), "accuracy");
525 math_equation_get_accuracy(MathEquation
*equation
)
527 g_return_val_if_fail(equation
!= NULL
, 0);
529 return mp_serializer_get_trailing_digits(equation
->priv
->serializer
);
534 math_equation_set_show_thousands_separators(MathEquation
*equation
, gboolean visible
)
536 g_return_if_fail(equation
!= NULL
);
538 if (mp_serializer_get_show_thousands_separators(equation
->priv
->serializer
) == visible
)
541 mp_serializer_set_show_thousands_separators(equation
->priv
->serializer
, visible
);
542 reformat_display(equation
);
543 g_object_notify(G_OBJECT(equation
), "show-thousands-separators");
548 math_equation_get_show_thousands_separators(MathEquation
*equation
)
550 g_return_val_if_fail(equation
!= NULL
, FALSE
);
551 return mp_serializer_get_show_thousands_separators(equation
->priv
->serializer
);
556 math_equation_set_show_trailing_zeroes(MathEquation
*equation
, gboolean visible
)
558 g_return_if_fail(equation
!= NULL
);
560 if (mp_serializer_get_show_trailing_zeroes(equation
->priv
->serializer
) == visible
)
563 mp_serializer_set_show_trailing_zeroes(equation
->priv
->serializer
, visible
);
564 reformat_display(equation
);
565 g_object_notify(G_OBJECT(equation
), "show-trailing-zeroes");
570 math_equation_get_show_trailing_zeroes(MathEquation
*equation
)
572 g_return_val_if_fail(equation
!= NULL
, FALSE
);
573 return mp_serializer_get_show_trailing_zeroes(equation
->priv
->serializer
);
578 math_equation_set_number_format(MathEquation
*equation
, MpDisplayFormat format
)
580 g_return_if_fail(equation
!= NULL
);
582 if (mp_serializer_get_number_format(equation
->priv
->serializer
) == format
)
585 mp_serializer_set_number_format(equation
->priv
->serializer
, format
);
586 reformat_display(equation
);
587 g_object_notify(G_OBJECT(equation
), "number-format");
592 math_equation_get_number_format(MathEquation
*equation
)
594 g_return_val_if_fail(equation
!= NULL
, MP_DISPLAY_FORMAT_AUTOMATIC
);
595 return mp_serializer_get_number_format(equation
->priv
->serializer
);
600 math_equation_set_base(MathEquation
*equation
, gint base
)
602 g_return_if_fail(equation
!= NULL
);
604 if (mp_serializer_get_base(equation
->priv
->serializer
) == base
)
607 mp_serializer_set_base(equation
->priv
->serializer
, base
);
608 reformat_display(equation
);
609 g_object_notify(G_OBJECT(equation
), "base");
614 math_equation_get_base(MathEquation
*equation
)
616 g_return_val_if_fail(equation
!= NULL
, 10);
617 return mp_serializer_get_base(equation
->priv
->serializer
);
622 math_equation_set_word_size(MathEquation
*equation
, gint word_size
)
624 g_return_if_fail(equation
!= NULL
);
626 if (equation
->priv
->word_size
== word_size
)
629 equation
->priv
->word_size
= word_size
;
630 g_object_notify(G_OBJECT(equation
), "word-size");
635 math_equation_get_word_size(MathEquation
*equation
)
637 g_return_val_if_fail(equation
!= NULL
, 64);
638 return equation
->priv
->word_size
;
643 math_equation_set_angle_units(MathEquation
*equation
, MPAngleUnit angle_units
)
645 g_return_if_fail(equation
!= NULL
);
647 if (equation
->priv
->angle_units
== angle_units
)
650 equation
->priv
->angle_units
= angle_units
;
651 g_object_notify(G_OBJECT(equation
), "angle-units");
656 math_equation_get_angle_units(MathEquation
*equation
)
658 g_return_val_if_fail(equation
!= NULL
, MP_DEGREES
);
659 return equation
->priv
->angle_units
;
664 math_equation_set_source_currency(MathEquation
*equation
, const gchar
*currency
)
666 g_return_if_fail(equation
!= NULL
);
667 g_return_if_fail(currency
!= NULL
);
669 if (strcmp(equation
->priv
->source_currency
, currency
) == 0)
671 g_free(equation
->priv
->source_currency
);
672 equation
->priv
->source_currency
= g_strdup(currency
);
673 g_object_notify(G_OBJECT(equation
), "source-currency");
678 math_equation_get_source_currency(MathEquation
*equation
)
680 g_return_val_if_fail(equation
!= NULL
, NULL
);
681 return equation
->priv
->source_currency
;
686 math_equation_set_target_currency(MathEquation
*equation
, const gchar
*currency
)
688 g_return_if_fail(equation
!= NULL
);
689 g_return_if_fail(currency
!= NULL
);
691 if (strcmp(equation
->priv
->target_currency
, currency
) == 0)
693 g_free(equation
->priv
->target_currency
);
694 equation
->priv
->target_currency
= g_strdup(currency
);
695 g_object_notify(G_OBJECT(equation
), "target-currency");
700 math_equation_get_target_currency(MathEquation
*equation
)
702 g_return_val_if_fail(equation
!= NULL
, NULL
);
703 return equation
->priv
->target_currency
;
708 math_equation_set_source_units(MathEquation
*equation
, const gchar
*units
)
710 g_return_if_fail(equation
!= NULL
);
711 g_return_if_fail(units
!= NULL
);
713 if (strcmp(equation
->priv
->source_units
, units
) == 0)
716 g_free(equation
->priv
->source_units
);
717 equation
->priv
->source_units
= g_strdup(units
);
718 g_object_notify(G_OBJECT(equation
), "source-units");
722 math_equation_get_source_units(MathEquation
*equation
)
724 g_return_val_if_fail(equation
!= NULL
, NULL
);
725 return equation
->priv
->source_units
;
730 math_equation_set_target_units(MathEquation
*equation
, const gchar
*units
)
732 g_return_if_fail(equation
!= NULL
);
733 g_return_if_fail(units
!= NULL
);
735 if (strcmp(equation
->priv
->target_units
, units
) == 0)
738 g_free(equation
->priv
->target_units
);
739 equation
->priv
->target_units
= g_strdup(units
);
740 g_object_notify(G_OBJECT(equation
), "target-units");
745 math_equation_get_target_units(MathEquation
*equation
)
747 g_return_val_if_fail(equation
!= NULL
, NULL
);
748 return equation
->priv
->target_units
;
753 math_equation_set_status(MathEquation
*equation
, const gchar
*status
)
755 g_return_if_fail(equation
!= NULL
);
756 g_return_if_fail(status
!= NULL
);
758 if (strcmp(equation
->priv
->state
.status
, status
) == 0)
761 g_free(equation
->priv
->state
.status
);
762 equation
->priv
->state
.status
= g_strdup(status
);
763 g_object_notify(G_OBJECT(equation
), "status");
768 math_equation_get_status(MathEquation
*equation
)
770 g_return_val_if_fail(equation
!= NULL
, NULL
);
771 return equation
->priv
->state
.status
;
776 math_equation_is_empty(MathEquation
*equation
)
778 g_return_val_if_fail(equation
!= NULL
, FALSE
);
779 return gtk_text_buffer_get_char_count(GTK_TEXT_BUFFER(equation
)) == 0;
784 math_equation_is_result(MathEquation
*equation
)
789 g_return_val_if_fail(equation
!= NULL
, FALSE
);
791 text
= math_equation_get_equation(equation
);
792 result
= strcmp(text
, "ans") == 0;
800 math_equation_get_display(MathEquation
*equation
)
802 GtkTextIter start
, end
;
804 g_return_val_if_fail(equation
!= NULL
, NULL
);
806 gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(equation
), &start
, &end
);
807 return gtk_text_buffer_get_text(GTK_TEXT_BUFFER(equation
), &start
, &end
, FALSE
);
812 math_equation_get_equation(MathEquation
*equation
)
816 gint ans_start
= -1, ans_end
= -1, offset
;
817 const gchar
*read_iter
;
818 gboolean last_is_digit
= FALSE
;
820 g_return_val_if_fail(equation
!= NULL
, NULL
);
822 text
= math_equation_get_display(equation
);
823 eq_text
= g_string_sized_new(strlen(text
));
825 if (equation
->priv
->ans_start
)
826 get_ans_offsets(equation
, &ans_start
, &ans_end
);
828 for (read_iter
= text
, offset
= 0; *read_iter
!= '\0'; read_iter
= g_utf8_next_char(read_iter
), offset
++) {
830 gboolean is_digit
, next_is_digit
;
832 c
= g_utf8_get_char(read_iter
);
833 is_digit
= g_unichar_isdigit(c
);
834 next_is_digit
= g_unichar_isdigit(g_utf8_get_char(g_utf8_next_char(read_iter
)));
836 /* Replace ans text with variable */
837 if (offset
== ans_start
) {
838 g_string_append(eq_text
, "ans");
839 read_iter
= g_utf8_offset_to_pointer(read_iter
, ans_end
- ans_start
- 1);
840 offset
+= ans_end
- ans_start
- 1;
845 /* Ignore thousands separators */
846 if (c
== mp_serializer_get_thousands_separator(equation
->priv
->serializer
) && last_is_digit
&& next_is_digit
)
848 /* Substitute radix character */
849 else if (c
== mp_serializer_get_radix(equation
->priv
->serializer
) && (last_is_digit
|| next_is_digit
))
850 g_string_append_unichar(eq_text
, '.');
852 g_string_append_unichar(eq_text
, c
);
854 last_is_digit
= is_digit
;
859 g_string_free(eq_text
, FALSE
);
866 math_equation_get_number(MathEquation
*equation
, MPNumber
*z
)
871 g_return_val_if_fail(equation
!= NULL
, FALSE
);
872 g_return_val_if_fail(z
!= NULL
, FALSE
);
874 if (math_equation_is_result(equation
)) {
875 mp_set_from_mp(math_equation_get_answer(equation
), z
);
879 text
= math_equation_get_equation(equation
);
880 result
= !mp_serializer_from_string(equation
->priv
->serializer
, text
, z
);
888 math_equation_get_serializer(MathEquation
*equation
)
890 g_return_val_if_fail(equation
!= NULL
, NULL
);
891 return equation
->priv
->serializer
;
896 math_equation_set_number_mode(MathEquation
*equation
, NumberMode mode
)
898 g_return_if_fail(equation
!= NULL
);
900 if (equation
->priv
->number_mode
== mode
)
903 equation
->priv
->can_super_minus
= mode
== SUPERSCRIPT
;
905 equation
->priv
->number_mode
= mode
;
906 g_object_notify(G_OBJECT(equation
), "number-mode");
911 math_equation_get_number_mode(MathEquation
*equation
)
913 g_return_val_if_fail(equation
!= NULL
, NORMAL
);
914 return equation
->priv
->number_mode
;
919 math_equation_in_solve(MathEquation
*equation
)
921 g_return_val_if_fail(equation
!= NULL
, FALSE
);
922 return equation
->priv
->in_solve
;
927 math_equation_get_answer(MathEquation
*equation
)
929 g_return_val_if_fail(equation
!= NULL
, FALSE
);
930 return &equation
->priv
->state
.ans
;
935 math_equation_store(MathEquation
*equation
, const gchar
*name
)
939 g_return_if_fail(equation
!= NULL
);
940 g_return_if_fail(name
!= NULL
);
942 if (!math_equation_get_number(equation
, &t
))
943 math_equation_set_status(equation
, _("No sane value to store"));
945 math_variables_set(equation
->priv
->variables
, name
, &t
);
950 math_equation_recall(MathEquation
*equation
, const gchar
*name
)
952 g_return_if_fail(equation
!= NULL
);
953 g_return_if_fail(name
!= NULL
);
954 math_equation_insert(equation
, name
);
959 math_equation_set(MathEquation
*equation
, const gchar
*text
)
961 g_return_if_fail(equation
!= NULL
);
962 g_return_if_fail(text
!= NULL
);
963 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation
), text
, -1);
964 clear_ans(equation
, FALSE
);
969 math_equation_set_number(MathEquation
*equation
, const MPNumber
*x
)
972 GtkTextIter start
, end
;
974 g_return_if_fail(equation
!= NULL
);
975 g_return_if_fail(x
!= NULL
);
977 /* Show the number in the user chosen format */
978 text
= mp_serializer_to_string(equation
->priv
->serializer
, x
);
979 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation
), text
, -1);
980 mp_set_from_mp(x
, &equation
->priv
->state
.ans
);
982 /* Mark this text as the answer variable */
983 gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(equation
), &start
, &end
);
984 clear_ans(equation
, FALSE
);
985 equation
->priv
->ans_start
= gtk_text_buffer_create_mark(GTK_TEXT_BUFFER(equation
), NULL
, &start
, FALSE
);
986 equation
->priv
->ans_end
= gtk_text_buffer_create_mark(GTK_TEXT_BUFFER(equation
), NULL
, &end
, TRUE
);
987 gtk_text_buffer_apply_tag(GTK_TEXT_BUFFER(equation
), equation
->priv
->ans_tag
, &start
, &end
);
993 math_equation_insert(MathEquation
*equation
, const gchar
*text
)
995 g_return_if_fail(equation
!= NULL
);
996 g_return_if_fail(text
!= NULL
);
998 /* Replace ** with ^ (not on all keyboards) */
999 if (!gtk_text_buffer_get_has_selection(GTK_TEXT_BUFFER(equation
)) &&
1000 strcmp(text
, "×") == 0 && equation
->priv
->state
.entered_multiply
) {
1003 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation
), &iter
, gtk_text_buffer_get_insert(GTK_TEXT_BUFFER(equation
)));
1004 gtk_text_buffer_backspace(GTK_TEXT_BUFFER(equation
), &iter
, TRUE
, TRUE
);
1005 gtk_text_buffer_insert_at_cursor(GTK_TEXT_BUFFER(equation
), "^", -1);
1009 /* Can't enter superscript minus after entering digits */
1010 if (strstr("⁰¹²³⁴⁵⁶⁷⁸⁹", text
) != NULL
|| strcmp("⁻", text
) == 0)
1011 equation
->priv
->can_super_minus
= FALSE
;
1013 /* Disable super/subscript mode when finished entering */
1014 if (strstr("⁻⁰¹²³⁴⁵⁶⁷⁸⁹₀₁₂₃₄₅₆₇₈₉", text
) == NULL
)
1015 math_equation_set_number_mode(equation
, NORMAL
);
1017 gtk_text_buffer_delete_selection(GTK_TEXT_BUFFER(equation
), FALSE
, FALSE
);
1018 gtk_text_buffer_insert_at_cursor(GTK_TEXT_BUFFER(equation
), text
, -1);
1023 math_equation_insert_digit(MathEquation
*equation
, guint digit
)
1025 static const char *subscript_digits
[] = {"₀", "₁", "₂", "₃", "₄", "₅", "₆", "₇", "₈", "₉", NULL
};
1026 static const char *superscript_digits
[] = {"⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹", NULL
};
1028 g_return_if_fail(equation
!= NULL
);
1029 g_return_if_fail(digit
< 16);
1031 if (equation
->priv
->number_mode
== NORMAL
|| digit
>= 10) {
1034 len
= g_unichar_to_utf8(math_equation_get_digit_text(equation
, digit
), buffer
);
1036 math_equation_insert(equation
, buffer
);
1038 else if (equation
->priv
->number_mode
== SUPERSCRIPT
)
1039 math_equation_insert(equation
, superscript_digits
[digit
]);
1040 else if (equation
->priv
->number_mode
== SUBSCRIPT
)
1041 math_equation_insert(equation
, subscript_digits
[digit
]);
1046 math_equation_insert_numeric_point(MathEquation
*equation
)
1051 g_return_if_fail(equation
!= NULL
);
1053 len
= g_unichar_to_utf8(mp_serializer_get_radix(equation
->priv
->serializer
), buffer
);
1055 math_equation_insert(equation
, buffer
);
1060 math_equation_insert_number(MathEquation
*equation
, const MPNumber
*x
)
1064 g_return_if_fail(equation
!= NULL
);
1065 g_return_if_fail(x
!= NULL
);
1067 text
= mp_serializer_to_string(equation
->priv
->serializer
, x
);
1068 math_equation_insert(equation
, text
);
1074 math_equation_insert_exponent(MathEquation
*equation
)
1076 g_return_if_fail(equation
!= NULL
);
1077 math_equation_insert(equation
, "×10");
1078 math_equation_set_number_mode(equation
, SUPERSCRIPT
);
1083 math_equation_insert_subtract(MathEquation
*equation
)
1085 g_return_if_fail(equation
!= NULL
);
1086 if (equation
->priv
->number_mode
== SUPERSCRIPT
&& equation
->priv
->can_super_minus
) {
1087 math_equation_insert(equation
, "⁻");
1088 equation
->priv
->can_super_minus
= FALSE
;
1091 math_equation_insert(equation
, "−");
1092 math_equation_set_number_mode(equation
, NORMAL
);
1098 variable_is_defined(const char *name
, void *data
)
1100 MathEquation
*equation
= data
;
1101 char *c
, *lower_name
;
1103 lower_name
= strdup(name
);
1104 for (c
= lower_name
; *c
; c
++)
1107 if (strcmp(lower_name
, "rand") == 0 ||
1108 strcmp(lower_name
, "ans") == 0) {
1114 return math_variables_get(equation
->priv
->variables
, name
) != NULL
;
1119 get_variable(const char *name
, MPNumber
*z
, void *data
)
1121 char *c
, *lower_name
;
1123 MathEquation
*equation
= data
;
1126 lower_name
= strdup(name
);
1127 for (c
= lower_name
; *c
; c
++)
1130 if (strcmp(lower_name
, "rand") == 0)
1131 mp_set_from_random(z
);
1132 else if (strcmp(lower_name
, "ans") == 0)
1133 mp_set_from_mp(&equation
->priv
->state
.ans
, z
);
1135 t
= math_variables_get(equation
->priv
->variables
, name
);
1137 mp_set_from_mp(t
, z
);
1149 set_variable(const char *name
, const MPNumber
*x
, void *data
)
1151 MathEquation
*equation
= data
;
1152 /* FIXME: Don't allow writing to built-in variables, e.g. ans, rand, sin, ... */
1153 math_variables_set(equation
->priv
->variables
, name
, x
);
1158 convert(const MPNumber
*x
, const char *x_units
, const char *z_units
, MPNumber
*z
, void *data
)
1160 return unit_manager_convert_by_symbol(unit_manager_get_default(), x
, x_units
, z_units
, z
);
1165 parse(MathEquation
*equation
, const char *text
, MPNumber
*z
, char **error_token
)
1167 MPEquationOptions options
;
1169 memset(&options
, 0, sizeof(options
));
1170 options
.base
= mp_serializer_get_base(equation
->priv
->serializer
);
1171 options
.wordlen
= equation
->priv
->word_size
;
1172 options
.angle_units
= equation
->priv
->angle_units
;
1173 options
.variable_is_defined
= variable_is_defined
;
1174 options
.get_variable
= get_variable
;
1175 options
.set_variable
= set_variable
;
1176 options
.convert
= convert
;
1177 options
.callback_data
= equation
;
1179 return mp_equation_parse(text
, &options
, z
, error_token
);
1184 * Executed in separate thread. It is thus not a good idea to write to anything
1185 * in MathEquation but the async queue from here.
1188 math_equation_solve_real(gpointer data
)
1190 MathEquation
*equation
= MATH_EQUATION(data
);
1191 SolveData
*solvedata
= g_slice_new0(SolveData
);
1193 gint n_brackets
= 0, result
;
1194 gchar
*c
, *text
, *error_token
;
1195 GString
*equation_text
;
1198 text
= math_equation_get_equation(equation
);
1199 equation_text
= g_string_new(text
);
1201 /* Count the number of brackets and automatically add missing closing brackets */
1202 for (c
= equation_text
->str
; *c
; c
++) {
1208 while (n_brackets
> 0) {
1209 g_string_append_c(equation_text
, ')');
1214 result
= parse(equation
, equation_text
->str
, &z
, &error_token
);
1215 g_string_free(equation_text
, TRUE
);
1218 case PARSER_ERR_NONE
:
1219 solvedata
->number_result
= g_slice_new(MPNumber
);
1220 mp_set_from_mp(&z
, solvedata
->number_result
);
1223 case PARSER_ERR_OVERFLOW
:
1224 solvedata
->error
= g_strdup(/* Error displayed to user when they perform a bitwise operation on numbers greater than the current word */
1225 _("Overflow. Try a bigger word size"));
1228 case PARSER_ERR_UNKNOWN_VARIABLE
:
1229 solvedata
->error
= g_strdup_printf(/* Error displayed to user when they an unknown variable is entered */
1230 _("Unknown variable '%s'"), error_token
);
1233 case PARSER_ERR_UNKNOWN_FUNCTION
:
1234 solvedata
->error
= g_strdup_printf(/* Error displayed to user when an unknown function is entered */
1235 _("Function '%s' is not defined"), error_token
);
1238 case PARSER_ERR_UNKNOWN_CONVERSION
:
1239 solvedata
->error
= g_strdup(/* Error displayed to user when an conversion with unknown units is attempted */
1240 _("Unknown conversion"));
1244 solvedata
->error
= g_strdup(mp_get_error());
1248 solvedata
->error
= g_strdup(/* Error displayed to user when they enter an invalid calculation */
1249 _("Malformed expression"));
1252 g_async_queue_push(equation
->priv
->queue
, solvedata
);
1259 math_equation_show_in_progress(gpointer data
)
1261 MathEquation
*equation
= MATH_EQUATION(data
);
1262 if (equation
->priv
->in_solve
)
1263 math_equation_set_status(equation
, _("Calculating"));
1269 math_equation_look_for_answer(gpointer data
)
1271 MathEquation
*equation
= MATH_EQUATION(data
);
1272 SolveData
*result
= g_async_queue_try_pop(equation
->priv
->queue
);
1277 equation
->priv
->in_solve
= false;
1280 math_equation_set_status(equation
, "");
1282 if (result
->error
!= NULL
) {
1283 math_equation_set_status(equation
, result
->error
);
1284 g_free(result
->error
);
1286 else if (result
->number_result
!= NULL
) {
1287 math_equation_set_number(equation
, result
->number_result
);
1288 g_slice_free(MPNumber
, result
->number_result
);
1290 else if (result
->text_result
!= NULL
) {
1291 math_equation_set(equation
, result
->text_result
);
1292 g_free(result
->text_result
);
1294 g_slice_free(SolveData
, result
);
1301 math_equation_solve(MathEquation
*equation
)
1303 g_return_if_fail(equation
!= NULL
);
1305 // FIXME: should replace calculation or give error message
1306 if (equation
->priv
->in_solve
)
1309 if (math_equation_is_empty(equation
))
1312 /* If showing a result return to the equation that caused it */
1313 // FIXME: Result may not be here due to solve (i.e. the user may have entered "ans")
1314 if (math_equation_is_result(equation
)) {
1315 math_equation_undo(equation
);
1319 equation
->priv
->in_solve
= true;
1321 math_equation_set_number_mode(equation
, NORMAL
);
1323 g_thread_new("", math_equation_solve_real
, equation
);
1325 g_timeout_add(50, math_equation_look_for_answer
, equation
);
1326 g_timeout_add(100, math_equation_show_in_progress
, equation
);
1331 math_equation_factorize_real(gpointer data
)
1334 GList
*factors
, *factor
;
1336 MathEquation
*equation
= MATH_EQUATION(data
);
1337 SolveData
*result
= g_slice_new0(SolveData
);
1339 math_equation_get_number(equation
, &x
);
1340 factors
= mp_factorize(&x
);
1342 text
= g_string_new("");
1344 for (factor
= factors
; factor
; factor
= factor
->next
) {
1349 temp
= mp_serializer_to_string(equation
->priv
->serializer
, n
);
1350 g_string_append(text
, temp
);
1352 g_string_append(text
, "×");
1353 g_slice_free(MPNumber
, n
);
1356 g_list_free(factors
);
1358 result
->text_result
= g_strndup(text
->str
, text
->len
);
1359 g_async_queue_push(equation
->priv
->queue
, result
);
1360 g_string_free(text
, TRUE
);
1367 math_equation_factorize(MathEquation
*equation
)
1371 g_return_if_fail(equation
!= NULL
);
1373 // FIXME: should replace calculation or give error message
1374 if (equation
->priv
->in_solve
)
1377 if (!math_equation_get_number(equation
, &x
) || !mp_is_integer(&x
)) {
1378 /* Error displayed when trying to factorize a non-integer value */
1379 math_equation_set_status(equation
, _("Need an integer to factorize"));
1383 equation
->priv
->in_solve
= true;
1385 g_thread_new("", math_equation_factorize_real
, equation
);
1387 g_timeout_add(50, math_equation_look_for_answer
, equation
);
1388 g_timeout_add(100, math_equation_show_in_progress
, equation
);
1393 math_equation_delete(MathEquation
*equation
)
1396 GtkTextIter start
, end
;
1398 g_return_if_fail(equation
!= NULL
);
1400 g_object_get(G_OBJECT(equation
), "cursor-position", &cursor
, NULL
);
1401 if (cursor
>= gtk_text_buffer_get_char_count(GTK_TEXT_BUFFER(equation
)))
1404 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation
), &start
, cursor
);
1405 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation
), &end
, cursor
+1);
1406 gtk_text_buffer_delete(GTK_TEXT_BUFFER(equation
), &start
, &end
);
1411 math_equation_backspace(MathEquation
*equation
)
1413 g_return_if_fail(equation
!= NULL
);
1415 /* Can't delete empty display */
1416 if (math_equation_is_empty(equation
))
1419 if (gtk_text_buffer_get_has_selection(GTK_TEXT_BUFFER(equation
)))
1420 gtk_text_buffer_delete_selection(GTK_TEXT_BUFFER(equation
), FALSE
, FALSE
);
1423 gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation
), &iter
, gtk_text_buffer_get_insert(GTK_TEXT_BUFFER(equation
)));
1424 gtk_text_buffer_backspace(GTK_TEXT_BUFFER(equation
), &iter
, TRUE
, TRUE
);
1430 math_equation_clear(MathEquation
*equation
)
1432 g_return_if_fail(equation
!= NULL
);
1434 math_equation_set_number_mode(equation
, NORMAL
);
1435 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation
), "", -1);
1436 clear_ans(equation
, FALSE
);
1441 math_equation_shift(MathEquation
*equation
, gint count
)
1445 g_return_if_fail(equation
!= NULL
);
1447 if (!math_equation_get_number(equation
, &z
)) {
1448 math_equation_set_status(equation
,
1449 /* This message is displayed in the status bar when a bit
1450 shift operation is performed and the display does not contain a number */
1451 _("No sane value to bitwise shift"));
1455 mp_shift(&z
, count
, &z
);
1456 math_equation_set_number(equation
, &z
);
1461 math_equation_toggle_bit(MathEquation
*equation
, guint bit
)
1467 g_return_if_fail(equation
!= NULL
);
1469 result
= math_equation_get_number(equation
, &x
);
1472 mp_set_from_unsigned_integer(G_MAXUINT64
, &max
);
1473 if (mp_is_negative(&x
) || mp_is_greater_than(&x
, &max
))
1476 bits
= mp_cast_to_unsigned_int(&x
);
1480 math_equation_set_status(equation
,
1481 /* Message displayed when cannot toggle bit in display*/
1482 _("Displayed value not an integer"));
1486 bits
^= (1LL << (63 - bit
));
1488 mp_set_from_unsigned_integer(bits
, &x
);
1490 // FIXME: Only do this if in ans format, otherwise set text in same format as previous number
1491 math_equation_set_number(equation
, &x
);
1496 math_equation_set_property(GObject
*object
,
1498 const GValue
*value
,
1503 self
= MATH_EQUATION(object
);
1507 math_equation_set_status(self
, g_value_get_string(value
));
1510 math_equation_set(self
, g_value_get_string(value
));
1512 case PROP_NUMBER_MODE
:
1513 math_equation_set_number_mode(self
, g_value_get_int(value
));
1516 math_equation_set_accuracy(self
, g_value_get_int(value
));
1518 case PROP_SHOW_THOUSANDS_SEPARATORS
:
1519 math_equation_set_show_thousands_separators(self
, g_value_get_boolean(value
));
1521 case PROP_SHOW_TRAILING_ZEROES
:
1522 math_equation_set_show_trailing_zeroes(self
, g_value_get_boolean(value
));
1524 case PROP_NUMBER_FORMAT
:
1525 math_equation_set_number_format(self
, g_value_get_int(value
));
1528 math_equation_set_base(self
, g_value_get_int(value
));
1530 case PROP_WORD_SIZE
:
1531 math_equation_set_word_size(self
, g_value_get_int(value
));
1533 case PROP_ANGLE_UNITS
:
1534 math_equation_set_angle_units(self
, g_value_get_int(value
));
1536 case PROP_SOURCE_CURRENCY
:
1537 math_equation_set_source_currency(self
, g_value_get_string(value
));
1539 case PROP_TARGET_CURRENCY
:
1540 math_equation_set_target_currency(self
, g_value_get_string(value
));
1542 case PROP_SOURCE_UNITS
:
1543 math_equation_set_source_units(self
, g_value_get_string(value
));
1545 case PROP_TARGET_UNITS
:
1546 math_equation_set_target_units(self
, g_value_get_string(value
));
1549 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
1556 math_equation_get_property(GObject
*object
,
1564 self
= MATH_EQUATION(object
);
1568 g_value_set_string(value
, self
->priv
->state
.status
);
1571 text
= math_equation_get_display(self
);
1572 g_value_set_string(value
, text
);
1576 text
= math_equation_get_equation(self
);
1577 g_value_set_string(value
, text
);
1580 case PROP_NUMBER_MODE
:
1581 g_value_set_enum(value
, self
->priv
->number_mode
);
1584 g_value_set_int(value
, mp_serializer_get_trailing_digits(self
->priv
->serializer
));
1586 case PROP_SHOW_THOUSANDS_SEPARATORS
:
1587 g_value_set_boolean(value
, mp_serializer_get_show_thousands_separators(self
->priv
->serializer
));
1589 case PROP_SHOW_TRAILING_ZEROES
:
1590 g_value_set_boolean(value
, mp_serializer_get_show_trailing_zeroes(self
->priv
->serializer
));
1592 case PROP_NUMBER_FORMAT
:
1593 g_value_set_enum(value
, mp_serializer_get_number_format(self
->priv
->serializer
));
1596 g_value_set_int(value
, math_equation_get_base(self
));
1598 case PROP_WORD_SIZE
:
1599 g_value_set_int(value
, self
->priv
->word_size
);
1601 case PROP_ANGLE_UNITS
:
1602 g_value_set_enum(value
, self
->priv
->angle_units
);
1604 case PROP_SOURCE_CURRENCY
:
1605 g_value_set_string(value
, self
->priv
->source_currency
);
1607 case PROP_TARGET_CURRENCY
:
1608 g_value_set_string(value
, self
->priv
->target_currency
);
1610 case PROP_SOURCE_UNITS
:
1611 g_value_set_string(value
, self
->priv
->source_units
);
1613 case PROP_TARGET_UNITS
:
1614 g_value_set_string(value
, self
->priv
->target_units
);
1616 case PROP_SERIALIZER
:
1617 g_value_set_object(value
, self
->priv
->serializer
);
1620 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
1627 math_equation_constructed(GObject
*object
)
1629 GtkTextBuffer
*parent_class
;
1630 parent_class
= g_type_class_peek_parent(MATH_EQUATION_GET_CLASS(object
));
1631 if (G_OBJECT_CLASS(parent_class
)->constructed
)
1632 G_OBJECT_CLASS(parent_class
)->constructed(object
);
1634 MATH_EQUATION(object
)->priv
->ans_tag
= gtk_text_buffer_create_tag(GTK_TEXT_BUFFER(object
), NULL
, "weight", PANGO_WEIGHT_BOLD
, NULL
);
1639 math_equation_class_init(MathEquationClass
*klass
)
1641 static GEnumValue number_mode_values
[] =
1643 {NORMAL
, "normal", "normal"},
1644 {SUPERSCRIPT
, "superscript", "superscript"},
1645 {SUBSCRIPT
, "subscript", "subscript"},
1648 static GEnumValue angle_unit_values
[] =
1650 {MP_RADIANS
, "radians", "radians"},
1651 {MP_DEGREES
, "degrees", "degrees"},
1652 {MP_GRADIANS
, "gradians", "gradians"},
1655 GObjectClass
*object_class
= G_OBJECT_CLASS(klass
);
1657 object_class
->get_property
= math_equation_get_property
;
1658 object_class
->set_property
= math_equation_set_property
;
1659 object_class
->constructed
= math_equation_constructed
;
1661 g_type_class_add_private(klass
, sizeof(MathEquationPrivate
));
1663 number_mode_type
= g_enum_register_static("NumberMode", number_mode_values
);
1664 number_format_type
= math_mp_display_format_get_type();
1665 angle_unit_type
= g_enum_register_static("AngleUnit", angle_unit_values
);
1667 g_object_class_install_property(object_class
,
1669 g_param_spec_string("status",
1673 G_PARAM_READWRITE
));
1674 g_object_class_install_property(object_class
,
1676 g_param_spec_string("display",
1678 "Displayed equation text",
1680 G_PARAM_READWRITE
));
1681 g_object_class_install_property(object_class
,
1683 g_param_spec_string("equation",
1688 g_object_class_install_property(object_class
,
1690 g_param_spec_enum("number-mode",
1692 "Input number mode",
1695 G_PARAM_READWRITE
));
1696 g_object_class_install_property(object_class
,
1698 g_param_spec_int("accuracy",
1702 G_PARAM_READWRITE
));
1703 g_object_class_install_property(object_class
,
1704 PROP_SHOW_THOUSANDS_SEPARATORS
,
1705 g_param_spec_boolean("show-thousands-separators",
1706 "show-thousands-separators",
1707 "Show thousands separators",
1709 G_PARAM_READWRITE
));
1710 g_object_class_install_property(object_class
,
1711 PROP_SHOW_TRAILING_ZEROES
,
1712 g_param_spec_boolean("show-trailing-zeroes",
1713 "show-trailing-zeroes",
1714 "Show trailing zeroes",
1716 G_PARAM_READWRITE
));
1717 g_object_class_install_property(object_class
,
1719 g_param_spec_enum("number-format",
1723 MP_DISPLAY_FORMAT_FIXED
,
1724 G_PARAM_READWRITE
));
1725 g_object_class_install_property(object_class
,
1727 g_param_spec_int("base",
1729 "Default number base (derived from number-format)",
1731 G_PARAM_READWRITE
));
1732 g_object_class_install_property(object_class
,
1734 g_param_spec_int("word-size",
1736 "Word size in bits",
1738 G_PARAM_READWRITE
));
1739 g_object_class_install_property(object_class
,
1741 g_param_spec_enum("angle-units",
1746 G_PARAM_READWRITE
));
1747 g_object_class_install_property(object_class
,
1748 PROP_SOURCE_CURRENCY
,
1749 g_param_spec_string("source-currency",
1753 G_PARAM_READWRITE
));
1754 g_object_class_install_property(object_class
,
1755 PROP_TARGET_CURRENCY
,
1756 g_param_spec_string("target-currency",
1760 G_PARAM_READWRITE
));
1761 g_object_class_install_property(object_class
,
1763 g_param_spec_string("source-units",
1767 G_PARAM_READWRITE
));
1768 g_object_class_install_property(object_class
,
1770 g_param_spec_string("target-units",
1774 G_PARAM_READWRITE
));
1775 g_object_class_install_property(object_class
,
1777 g_param_spec_object("serializer",
1786 pre_insert_text_cb(MathEquation
*equation
,
1787 GtkTextIter
*location
,
1795 if (equation
->priv
->in_reformat
)
1798 /* If following a delete then have already pushed undo stack (GtkTextBuffer
1799 doesn't indicate replace operations so we have to infer them) */
1800 if (!equation
->priv
->in_delete
)
1801 math_equation_push_undo_stack(equation
);
1803 /* Clear result on next digit entered if cursor at end of line */
1804 c
= g_utf8_get_char(text
);
1805 g_object_get(G_OBJECT(equation
), "cursor-position", &cursor
, NULL
);
1806 if ((g_unichar_isdigit(c
) || c
== mp_serializer_get_radix(equation
->priv
->serializer
)) &&
1807 math_equation_is_result(equation
) &&
1808 cursor
>= gtk_text_buffer_get_char_count(GTK_TEXT_BUFFER(equation
))) {
1809 gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation
), "", -1);
1810 clear_ans(equation
, FALSE
);
1811 gtk_text_buffer_get_end_iter(GTK_TEXT_BUFFER(equation
), location
);
1814 if (equation
->priv
->ans_start
) {
1815 gint ans_start
, ans_end
;
1818 offset
= gtk_text_iter_get_offset(location
);
1819 get_ans_offsets(equation
, &ans_start
, &ans_end
);
1821 /* Inserted inside ans */
1822 if (offset
> ans_start
&& offset
< ans_end
)
1823 clear_ans(equation
, TRUE
);
1829 on_delete(MathEquation
*equation
)
1831 equation
->priv
->in_delete
= FALSE
;
1837 pre_delete_range_cb(MathEquation
*equation
,
1842 if (equation
->priv
->in_reformat
)
1845 math_equation_push_undo_stack(equation
);
1847 equation
->priv
->in_delete
= TRUE
;
1848 g_idle_add((GSourceFunc
)on_delete
, equation
);
1850 if (equation
->priv
->ans_start
) {
1851 gint ans_start
, ans_end
;
1852 gint start_offset
, end_offset
;
1854 start_offset
= gtk_text_iter_get_offset(start
);
1855 end_offset
= gtk_text_iter_get_offset(end
);
1856 get_ans_offsets(equation
, &ans_start
, &ans_end
);
1858 /* Deleted part of ans */
1859 if (start_offset
< ans_end
&& end_offset
> ans_start
)
1860 clear_ans(equation
, TRUE
);
1866 insert_text_cb(MathEquation
*equation
,
1867 GtkTextIter
*location
,
1872 if (equation
->priv
->in_reformat
)
1875 equation
->priv
->state
.entered_multiply
= strcmp(text
, "×") == 0;
1877 /* Update thousands separators */
1878 reformat_separators(equation
);
1880 g_object_notify(G_OBJECT(equation
), "display");
1885 delete_range_cb(MathEquation
*equation
,
1890 if (equation
->priv
->in_reformat
)
1893 equation
->priv
->state
.entered_multiply
= FALSE
;
1895 /* Update thousands separators */
1896 reformat_separators(equation
);
1898 // FIXME: A replace will emit this both for delete-range and insert-text, can it be avoided?
1899 g_object_notify(G_OBJECT(equation
), "display");
1904 math_equation_init(MathEquation
*equation
)
1906 /* Digits localized for the given language */
1907 const char *digit_values
= _("0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F");
1908 const char *default_digits
[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"};
1910 /* Default to using untranslated digits, this is because it doesn't make sense in most languages and we need to make this optional.
1911 * See https://bugzilla.gnome.org/show_bug.cgi?id=632661 */
1912 gboolean use_default_digits
= TRUE
;
1915 equation
->priv
= G_TYPE_INSTANCE_GET_PRIVATE(equation
, math_equation_get_type(), MathEquationPrivate
);
1917 g_signal_connect(equation
, "insert-text", G_CALLBACK(pre_insert_text_cb
), equation
);
1918 g_signal_connect(equation
, "delete-range", G_CALLBACK(pre_delete_range_cb
), equation
);
1919 g_signal_connect_after(equation
, "insert-text", G_CALLBACK(insert_text_cb
), equation
);
1920 g_signal_connect_after(equation
, "delete-range", G_CALLBACK(delete_range_cb
), equation
);
1922 digits
= g_strsplit(digit_values
, ",", -1);
1923 for (i
= 0; i
< 16; i
++) {
1924 if (use_default_digits
|| digits
[i
] == NULL
) {
1925 use_default_digits
= TRUE
;
1926 equation
->priv
->digits
[i
] = g_utf8_get_char(default_digits
[i
]);
1929 equation
->priv
->digits
[i
] = g_utf8_get_char(digits
[i
]);
1933 equation
->priv
->variables
= math_variables_new();
1935 equation
->priv
->state
.status
= g_strdup("");
1936 equation
->priv
->word_size
= 32;
1937 equation
->priv
->angle_units
= MP_DEGREES
;
1938 // FIXME: Pick based on locale
1939 equation
->priv
->source_currency
= g_strdup("");
1940 equation
->priv
->target_currency
= g_strdup("");
1941 equation
->priv
->source_units
= g_strdup("");
1942 equation
->priv
->target_units
= g_strdup("");
1943 equation
->priv
->serializer
= mp_serializer_new(MP_DISPLAY_FORMAT_AUTOMATIC
, 10, 9);
1944 equation
->priv
->queue
= g_async_queue_new();
1946 mp_set_from_integer(0, &equation
->priv
->state
.ans
);