2 * Copyright (C) 1987-2008 Sun Microsystems, Inc. All Rights Reserved.
3 * Copyright (C) 2008-2012 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
12 public enum NumberMode
19 /* Expression mode state */
20 private class MathEquationState
22 public Number ans
; /* Previously calculated answer */
23 public string expression
; /* Expression entered by user */
24 public int ans_start
; /* Start character for ans variable in expression */
25 public int ans_end
; /* End character for ans variable in expression */
26 public int cursor
; /* ??? */
27 public NumberMode number_mode
; /* ??? */
28 public bool can_super_minus
; /* true if entering minus can generate a superscript minus */
29 public bool entered_multiply
; /* Last insert was a multiply character */
30 public string status
; /* Equation status */
31 public uint error_token_start
; /* Start offset of error token */
32 public uint error_token_end
; /* End offset of error token */
35 private class SolveData
37 public Number? number_result
;
38 public string text_result
;
40 public uint error_start
;
41 public uint error_end
;
44 public class MathEquation
: Gtk
.TextBuffer
46 private Gtk
.TextTag ans_tag
;
48 /* Word size in bits */
49 private int _word_size
;
52 get { return _word_size
; }
55 if (_word_size
== value
)
58 notify_property ("word-size");
62 private string _source_currency
;
63 public string source_currency
65 owned
get { return _source_currency
; }
68 if (_source_currency
== value
)
70 _source_currency
= value
;
74 private string _target_currency
;
75 public string target_currency
77 owned
get { return _target_currency
; }
80 if (_target_currency
== value
)
82 _target_currency
= value
;
86 private string _source_units
;
87 public string source_units
89 owned
get { return _source_units
; }
92 if (_source_units
== value
)
94 _source_units
= value
;
98 private string _target_units
;
99 public string target_units
101 owned
get { return _target_units
; }
104 if (_target_units
== value
)
106 _target_units
= value
;
110 public string display
114 Gtk
.TextIter start
, end
;
115 get_bounds (out start
, out end
);
116 return get_text (start
, end
, false);
120 private AngleUnit _angle_units
; /* Units for trigonometric functions */
121 private NumberMode _number_mode
; /* ??? */
122 private bool can_super_minus
; /* true if entering minus can generate a superscript minus */
124 private unichar digits
[16]; /* Localized digits */
126 private Gtk
.TextMark? ans_start_mark
= null;
127 private Gtk
.TextMark? ans_end_mark
= null;
129 private MathEquationState state
; /* Equation state */
130 private List
<MathEquationState
> undo_stack
; /* History of expression mode states */
131 private List
<MathEquationState
> redo_stack
;
132 private bool in_undo_operation
;
134 private bool in_reformat
;
136 private bool in_delete
;
138 private bool _in_solve
;
141 get { return _in_solve
; }
144 private MathVariables _variables
;
145 public MathVariables variables
147 get { return _variables
; }
150 private Serializer _serializer
;
151 public Serializer serializer
153 get { return _serializer
; }
156 private AsyncQueue
<SolveData
> queue
;
158 public MathEquation ()
160 undo_stack
= new List
<MathEquationState
> ();
161 redo_stack
= new List
<MathEquationState
> ();
163 /* Default to using untranslated digits, this is because it doesn't make sense in most languages and we need to make this optional.
164 * See https://bugzilla.gnome.org/show_bug.cgi?id=632661 */
165 var use_default_digits
= true;
167 const unichar default_digits
[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
168 /* Digits localized for the given language */
169 var ds
= _("0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F").split (",", -1);
170 for (var i
= 0; i
< 16; i
++)
172 if (use_default_digits
|| ds
[i
] == null)
174 use_default_digits
= true;
175 digits
[i
] = default_digits
[i
];
178 digits
[i
] = ds
[i
].get_char (0);
181 _variables
= new
MathVariables ();
183 state
= new
MathEquationState ();
186 _angle_units
= AngleUnit
.DEGREES
;
187 // FIXME: Pick based on locale
188 source_currency
= "";
189 target_currency
= "";
192 _serializer
= new
Serializer (DisplayFormat
.AUTOMATIC
, 10, 9);
193 queue
= new AsyncQueue
<SolveData
> ();
195 state
.ans
= new Number
.integer (0);
197 ans_tag
= create_tag (null, "weight", Pango
.Weight
.BOLD
, null);
200 private void get_ans_offsets (out int start
, out int end
)
202 if (ans_start_mark
== null)
210 get_iter_at_mark (out iter
, ans_start_mark
);
211 start
= iter
.get_offset ();
212 get_iter_at_mark (out iter
, ans_end_mark
);
213 end
= iter
.get_offset ();
216 private void reformat_ans ()
218 if (ans_start_mark
== null)
221 Gtk
.TextIter ans_start
, ans_end
;
222 get_iter_at_mark (out ans_start
, ans_start_mark
);
223 get_iter_at_mark (out ans_end
, ans_end_mark
);
225 var orig_ans_text
= get_text (ans_start
, ans_end
, false);
226 var ans_text
= serializer
.to_string (state
.ans
);
227 if (orig_ans_text
!= ans_text
)
229 in_undo_operation
= true;
232 var start
= ans_start
.get_offset ();
233 @
delete (ref ans_start
, ref ans_end
);
234 insert_with_tags (ans_end
, ans_text
, -1, ans_tag
);
236 /* There seems to be a bug in the marks as they alternate being the correct and incorrect ways. Reset them */
237 get_iter_at_offset (out ans_start
, start
);
238 get_iter_at_offset (out ans_end
, start
+ ans_text
.length
);
239 move_mark (ans_start_mark
, ans_start
);
240 move_mark (ans_end_mark
, ans_end
);
243 in_undo_operation
= false;
245 get_iter_at_mark (out ans_start
, ans_start_mark
);
246 get_iter_at_mark (out ans_end
, ans_end_mark
);
249 private void reformat_separators ()
251 var in_number
= false;
252 var in_radix
= false;
253 var last_is_tsep
= false;
254 var digit_offset
= 0;
256 in_undo_operation
= true;
260 int ans_start
, ans_end
;
261 get_ans_offsets (out ans_start
, out ans_end
);
265 while (text
.get_next_char (ref index
, out c
))
269 var expect_tsep
= number_base
== 10 &&
270 serializer
.get_show_thousands_separators () &&
271 in_number
&& !in_radix
&& !last_is_tsep
&&
272 digit_offset
> 0 && digit_offset
% serializer
.get_thousands_separator_count () == 0;
273 last_is_tsep
= false;
275 /* Don't mess with ans */
276 if (offset
>= ans_start
&& offset
<= ans_end
)
278 in_number
= in_radix
= false;
284 digit_offset
= count_digits (text
, index
) + 1;
287 /* Expected a thousands separator between these digits - insert it */
291 get_iter_at_offset (out iter
, offset
);
292 (this as Gtk
.TextBuffer
).insert (ref iter
, serializer
.get_thousands_separator ().to_string (), -1);
299 else if (c
== serializer
.get_radix ())
304 else if (c
== serializer
.get_thousands_separator ())
306 /* Didn't expect thousands separator - delete it */
307 if (!expect_tsep
&& in_number
)
309 Gtk
.TextIter start
, end
;
310 get_iter_at_offset (out start
, offset
);
311 get_iter_at_offset (out end
, offset
+ 1);
312 @
delete (ref start
, ref end
);
326 in_undo_operation
= false;
329 private int count_digits (string text
, int index
)
332 var following_separator
= false;
334 while (text
.get_next_char (ref index
, out c
))
336 /* Allow a thousands separator between digits follow a digit */
337 if (c
== serializer
.get_thousands_separator ())
339 if (following_separator
)
341 following_separator
= true;
343 else if (c
.isdigit ())
345 following_separator
= false;
355 private void reformat_display ()
360 /* Add/remove thousands separators */
361 reformat_separators ();
364 private MathEquationState
get_current_state ()
366 int ans_start
= -1, ans_end
= -1;
368 if (ans_start_mark
!= null)
371 get_iter_at_mark (out iter
, ans_start_mark
);
372 ans_start
= iter
.get_offset ();
373 get_iter_at_mark (out iter
, ans_end_mark
);
374 ans_end
= iter
.get_offset ();
377 var s
= new
MathEquationState ();
379 s
.expression
= display
;
380 s
.ans_start
= ans_start
;
382 get ("cursor-position", out s
.cursor
, null);
383 s
.number_mode
= number_mode
;
384 s
.can_super_minus
= can_super_minus
;
385 s
.entered_multiply
= state
.entered_multiply
;
386 s
.status
= state
.status
;
391 private void push_undo_stack ()
393 if (in_undo_operation
)
398 /* Can't redo anymore */
399 redo_stack
= new List
<MathEquationState
> ();
401 state
= get_current_state ();
402 undo_stack
.prepend (state
);
405 private void clear_ans (bool do_remove_tag
)
407 if (ans_start_mark
== null)
412 Gtk
.TextIter start
, end
;
413 get_iter_at_mark (out start
, ans_start_mark
);
414 get_iter_at_mark (out end
, ans_end_mark
);
415 remove_tag (ans_tag
, start
, end
);
418 delete_mark (ans_start_mark
);
419 delete_mark (ans_end_mark
);
420 ans_start_mark
= null;
424 private void apply_state (MathEquationState s
)
426 /* Disable undo detection */
427 in_undo_operation
= true;
430 set_text (s
.expression
, -1);
432 get_iter_at_offset (out cursor
, s
.cursor
);
433 place_cursor (cursor
);
435 if (s
.ans_start
>= 0)
438 get_iter_at_offset (out start
, s
.ans_start
);
439 ans_start_mark
= create_mark (null, start
, false);
441 get_iter_at_offset (out end
, s
.ans_end
);
442 ans_end_mark
= create_mark (null, end
, true);
443 apply_tag (ans_tag
, start
, end
);
446 number_mode
= s
.number_mode
;
447 can_super_minus
= s
.can_super_minus
;
448 state
.entered_multiply
= s
.entered_multiply
;
451 in_undo_operation
= false;
456 Gtk
.TextIter start
, end
;
457 if (!get_selection_bounds (out start
, out end
))
458 get_bounds (out start
, out end
);
460 var text
= get_text (start
, end
, false);
461 Gtk
.Clipboard
.get (Gdk
.Atom
.NONE
).set_text (text
, -1);
466 Gtk
.Clipboard
.get (Gdk
.Atom
.NONE
).request_text (on_paste
);
469 private void on_paste (Gtk
.Clipboard clipboard
, string? text
)
477 if (undo_stack
== null)
479 /* Error shown when trying to undo with no undo history */
480 status
= _("No undo history");
484 state
= undo_stack
.nth_data (0);
485 undo_stack
.remove (state
);
486 redo_stack
.prepend (get_current_state ());
493 if (redo_stack
== null)
495 /* Error shown when trying to redo with no redo history */
496 status
= _("No redo history");
500 state
= redo_stack
.nth_data (0);
501 redo_stack
.remove (state
);
502 undo_stack
.prepend (get_current_state ());
507 public unichar
get_digit_text (uint digit
)
511 return digits
[digit
];
516 get { return serializer
.get_trailing_digits (); }
519 if (serializer
.get_trailing_digits () == value
)
521 serializer
.set_trailing_digits (value
);
523 notify_property ("accuracy");
527 public bool show_thousands_separators
529 get { return serializer
.get_show_thousands_separators (); }
532 if (serializer
.get_show_thousands_separators () == value
)
535 serializer
.set_show_thousands_separators (value
);
537 notify_property ("show-thousands-separators");
541 public bool show_trailing_zeroes
543 get { return serializer
.get_show_trailing_zeroes (); }
546 if (serializer
.get_show_trailing_zeroes () == value
)
549 serializer
.set_show_trailing_zeroes (value
);
551 notify_property ("show-trailing-zeroes");
555 public DisplayFormat number_format
557 get { return serializer
.get_number_format (); }
560 if (serializer
.get_number_format () == value
)
563 serializer
.set_number_format (value
);
565 notify_property ("number-format");
569 public int number_base
571 get { return serializer
.get_base (); }
574 if (serializer
.get_base () == value
)
577 serializer
.set_base (value
);
579 notify_property ("number-base");
583 public AngleUnit angle_units
585 get { return _angle_units
; }
588 if (_angle_units
== value
)
591 _angle_units
= value
;
592 notify_property ("angle-units");
598 owned
get { return state
.status
; }
601 if (state
.status
== value
)
604 state
.status
= value
;
605 notify_property ("status");
609 public uint error_token_start
613 /* Check if the previous answer is before start of error token.
614 * If so, subtract 3 (the length of string "ans") and add actual answer length (ans_end - ans_start) into it. */
615 int ans_start
, ans_end
;
616 get_ans_offsets (out ans_start
, out ans_end
);
617 if (ans_start
!= -1 && ans_start
< state
.error_token_start
)
618 return state
.error_token_start
+ ans_end
- ans_start
- 3;
620 return state
.error_token_start
;
624 public uint error_token_end
628 /* Check if the previous answer is before end of error token.
629 * If so, subtract 3 (the length of string "ans") and add actual answer length (ans_end - ans_start) into it. */
630 int ans_start
, ans_end
;
631 get_ans_offsets (out ans_start
, out ans_end
);
632 if (ans_start
!= -1 && ans_start
< state
.error_token_end
)
633 return state
.error_token_end
+ ans_end
- ans_start
- 3;
635 return state
.error_token_end
;
641 get { return get_char_count () == 0; }
644 public bool is_result
646 get { return equation
== "ans"; }
649 public string equation
656 var ans_start
= -1, ans_end
= -1;
657 if (ans_start_mark
!= null)
658 get_ans_offsets (out ans_start
, out ans_end
);
660 text
= text
.splice (text
.index_of_nth_char (ans_start
), text
.index_of_nth_char (ans_end
), "ans");
662 var last_is_digit
= false;
665 while (text
.get_next_char (ref index
, out c
))
667 var is_digit
= c
.isdigit ();
668 var next_is_digit
= false;
671 if (text
.get_next_char (ref i
, out next_char
))
672 next_is_digit
= next_char
.isdigit ();
674 /* Ignore thousands separators */
675 if (c
== serializer
.get_thousands_separator () && last_is_digit
&& next_is_digit
)
677 /* Substitute radix character */
678 else if (c
== serializer
.get_radix () && (last_is_digit
|| next_is_digit
))
681 eq_text
+= c
.to_string ();
683 last_is_digit
= is_digit
;
690 public Number? number
697 return serializer
.from_string (equation
);
701 public NumberMode number_mode
703 get { return _number_mode
; }
706 if (_number_mode
== value
)
709 can_super_minus
= value
== NumberMode
.SUPERSCRIPT
;
711 _number_mode
= value
;
712 notify_property ("number-mode");
718 get { return state
.ans
; }
721 public void store (string name
)
725 status
= _("No sane value to store");
727 variables
.set (name
, t
);
730 public void recall (string name
)
735 public new
void set (string text
)
741 public void set_number (Number x
)
743 /* Show the number in the user chosen format */
744 var text
= serializer
.to_string (x
);
748 /* Mark this text as the answer variable */
749 Gtk
.TextIter start
, end
;
750 get_bounds (out start
, out end
);
752 ans_start_mark
= create_mark (null, start
, false);
753 ans_end_mark
= create_mark (null, end
, true);
754 apply_tag (ans_tag
, start
, end
);
757 public new
void insert (string text
)
759 /* Replace ** with ^ (not on all keyboards) */
760 if (!has_selection
&& text
== "×" && state
.entered_multiply
)
763 get_iter_at_mark (out iter
, get_insert ());
764 (this as Gtk
.TextBuffer
).backspace (iter
, true, true);
765 insert_at_cursor ("^", -1);
769 /* Can't enter superscript minus after entering digits */
770 if ("⁰¹²³⁴⁵⁶⁷⁸⁹".index_of (text
) >= 0 || text
== "⁻")
771 can_super_minus
= false;
773 /* Disable super/subscript mode when finished entering */
774 if ("⁻⁰¹²³⁴⁵⁶⁷⁸⁹₀₁₂₃₄₅₆₇₈₉".index_of (text
) < 0)
775 number_mode
= NumberMode
.NORMAL
;
777 delete_selection (false, false);
778 insert_at_cursor (text
, -1);
781 public void insert_digit (uint digit
)
783 const unichar subscript_digits
[] = {'₀', '₁', '₂', '₃', '₄', '₅', '₆', '₇', '₈', '₉'};
784 const unichar superscript_digits
[] = {'⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'};
789 if (number_mode
== NumberMode
.NORMAL
|| digit
>= 10)
790 insert (get_digit_text (digit
).to_string ());
791 else if (number_mode
== NumberMode
.SUPERSCRIPT
)
792 insert (superscript_digits
[digit
].to_string ());
793 else if (number_mode
== NumberMode
.SUBSCRIPT
)
794 insert (subscript_digits
[digit
].to_string ());
797 public void insert_numeric_point ()
799 insert (serializer
.get_radix ().to_string ());
802 public void insert_number (Number x
)
804 insert (serializer
.to_string (x
));
807 public void insert_exponent ()
810 number_mode
= NumberMode
.SUPERSCRIPT
;
813 public void insert_subtract ()
815 if (number_mode
== NumberMode
.SUPERSCRIPT
&& can_super_minus
)
818 can_super_minus
= false;
823 number_mode
= NumberMode
.NORMAL
;
827 private Number?
parse (string text
, out ErrorCode error_code
= null, out string error_token
= null, out uint error_start
, out uint error_end
)
829 var equation
= new
MEquation (this
, text
);
830 equation
.base = serializer
.get_base ();
831 equation
.wordlen
= word_size
;
832 equation
.angle_units
= angle_units
;
834 return equation
.parse (out error_code
, out error_token
, out error_start
, out error_end
);
838 * Executed in separate thread. It is thus not a good idea to write to anything
839 * in MathEquation but the async queue from here.
841 private void* solve_real ()
843 var solvedata
= new
SolveData ();
846 /* Count the number of brackets and automatically add missing closing brackets */
848 for (var i
= 0; text
[i
] != '\0'; i
++)
852 else if (text
[i
] == ')')
855 while (n_brackets
> 0)
861 ErrorCode error_code
;
863 uint error_start
, error_end
;
864 var z
= parse (text
, out error_code
, out error_token
, out error_start
, out error_end
);
868 solvedata
.number_result
= z
;
871 case ErrorCode
.OVERFLOW
:
872 solvedata
.error
= /* Error displayed to user when they perform a bitwise operation on numbers greater than the current word */
873 _("Overflow. Try a bigger word size");
876 case ErrorCode
.UNKNOWN_VARIABLE
:
877 solvedata
.error
= /* Error displayed to user when they an unknown variable is entered */
878 _("Unknown variable '%s'").printf (error_token
);
879 solvedata
.error_start
= error_start
;
880 solvedata
.error_end
= error_end
;
883 case ErrorCode
.UNKNOWN_FUNCTION
:
884 solvedata
.error
= /* Error displayed to user when an unknown function is entered */
885 _("Function '%s' is not defined").printf (error_token
);
886 solvedata
.error_start
= error_start
;
887 solvedata
.error_end
= error_end
;
890 case ErrorCode
.UNKNOWN_CONVERSION
:
891 solvedata
.error
= /* Error displayed to user when an conversion with unknown units is attempted */
892 _("Unknown conversion");
896 if (mp_get_error () != null)
897 solvedata
.error
= mp_get_error ();
898 else if (error_token
!= null) /* Uncategorized error. Show error token to user */
900 solvedata
.error
= _("Malformed expression at token '%s'").printf (error_token
);
901 solvedata
.error_start
= error_start
;
902 solvedata
.error_end
= error_end
;
904 else /* Unknown error. */
905 solvedata
.error
= _("Malformed expression");
909 solvedata
.error
= /* Error displayed to user when they enter an invalid calculation */
910 _("Malformed expression");
913 queue
.push (solvedata
);
918 private bool show_in_progress ()
921 status
= _("Calculating");
925 private bool look_for_answer ()
927 var result
= queue
.try_pop ();
934 if (result
.error
== null)
937 if (result
.error
!= null)
939 status
= result
.error
;
940 state
.error_token_start
= result
.error_start
;
941 state
.error_token_end
= result
.error_end
;
943 /* Fix thousand separator offsets in the start and end offsets of error token. */
944 error_token_fix_thousands_separator ();
946 /* Notify the GUI about the change in error token locations. */
947 notify_property ("error-token-end");
949 else if (result
.number_result
!= null)
950 set_number (result
.number_result
);
951 else if (result
.text_result
!= null)
952 set (result
.text_result
);
959 // FIXME: should replace calculation or give error message
966 /* If showing a result return to the equation that caused it */
967 // FIXME: Result may not be here due to solve (i.e. the user may have entered "ans")
976 number_mode
= NumberMode
.NORMAL
;
978 new Thread
<void*> ("", solve_real
);
980 Timeout
.add (50, look_for_answer
);
981 Timeout
.add (100, show_in_progress
);
984 /* Fix the offsets to consider thousand separators inserted by the gui. */
985 private void error_token_fix_thousands_separator ()
988 get_start_iter (out start
);
992 start
.set_offset ((int) error_token_start
);
993 end
.set_offset ((int) error_token_end
);
995 var str
= serializer
.get_thousands_separator ().to_string ();
996 var length
= str
.char_count ();
998 /* Move both start and end offsets for each thousand separator till the start of error token. */
999 while (temp
.forward_search (str
, Gtk
.TextSearchFlags
.TEXT_ONLY
, null, out temp
, start
))
1001 state
.error_token_start
+= length
;
1002 state
.error_token_end
+= length
;
1003 start
.forward_chars (length
);
1004 start
.forward_chars (length
);
1007 /* Starting from start, move only end offset for each thousand separator till the end of error token. */
1009 while (temp
.forward_search (str
, Gtk
.TextSearchFlags
.TEXT_ONLY
, null, out temp
, end
))
1011 state
.error_token_end
+= length
;
1012 end
.forward_chars (length
);
1016 private void* factorize_real ()
1019 var factors
= x
.factorize ();
1023 foreach (var factor
in factors
)
1027 text
+= serializer
.to_string (factor
);
1031 var result
= new
SolveData ();
1032 result
.text_result
= text
;
1033 queue
.push (result
);
1038 public void factorize ()
1040 // FIXME: should replace calculation or give error message
1045 if (x
== null || !x
.is_integer ())
1047 /* Error displayed when trying to factorize a non-integer value */
1048 status
= _("Need an integer to factorize");
1054 new Thread
<void*> ("", factorize_real
);
1056 Timeout
.add (50, look_for_answer
);
1057 Timeout
.add (100, show_in_progress
);
1060 public void delete_next ()
1063 get ("cursor-position", out cursor
, null);
1064 if (cursor
>= get_char_count ())
1067 Gtk
.TextIter start
, end
;
1068 get_iter_at_offset (out start
, cursor
);
1069 get_iter_at_offset (out end
, cursor
+1);
1070 @
delete (ref start
, ref end
);
1073 public new
void backspace ()
1075 /* Can't delete empty display */
1080 delete_selection (false, false);
1084 get_iter_at_mark (out iter
, get_insert ());
1085 (this as Gtk
.TextBuffer
).backspace (iter
, true, true);
1089 public void clear ()
1091 number_mode
= NumberMode
.NORMAL
;
1096 public void shift (int count
)
1101 /* This message is displayed in the status bar when a bit shift operation is performed and the display does not contain a number */
1102 status
= _("No sane value to bitwise shift");
1106 set_number (z
.shift (count
));
1109 public void toggle_bit (uint bit
)
1112 var max
= new Number
.unsigned_integer (uint64.MAX
);
1113 if (x
== null || x
.is_negative () || x
.compare (max
) > 0)
1115 /* Message displayed when cannot toggle bit in display */
1116 status
= _("Displayed value not an integer");
1120 var bits
= x
.to_unsigned_integer ();
1121 bits ^
= (1LL << (63 - bit
));
1122 x
= new Number
.unsigned_integer (bits
);
1124 // FIXME: Only do this if in ans format, otherwise set text in same format as previous number
1128 protected override void insert_text (ref Gtk
.TextIter location
, string text
, int len
)
1132 base.insert_text (ref location
, text
, len
);
1136 /* If following a delete then have already pushed undo stack (Gtk.TextBuffer doesn't indicate replace operations so we have to infer them) */
1140 /* Clear result on next digit entered if cursor at end of line */
1141 var c
= text
.get_char (0);
1143 get ("cursor-position", out cursor
, null);
1144 if ((c
.isdigit () || c
== serializer
.get_radix ()) && is_result
&& cursor
>= get_char_count ())
1148 get_end_iter (out location
);
1151 if (ans_start_mark
!= null)
1153 var offset
= location
.get_offset ();
1154 int ans_start
, ans_end
;
1155 get_ans_offsets (out ans_start
, out ans_end
);
1157 /* Inserted inside ans */
1158 if (offset
> ans_start
&& offset
< ans_end
)
1162 base.insert_text (ref location
, text
, len
);
1164 state
.entered_multiply
= text
== "×";
1166 /* Update thousands separators */
1167 reformat_separators ();
1169 notify_property ("display");
1172 protected override void delete_range (Gtk
.TextIter start
, Gtk
.TextIter end
)
1176 base.delete_range (start
, end
);
1183 Idle
.add (() => { in_delete
= false; return false; });
1185 if (ans_start_mark
!= null)
1187 var start_offset
= start
.get_offset ();
1188 var end_offset
= end
.get_offset ();
1189 int ans_start
, ans_end
;
1190 get_ans_offsets (out ans_start
, out ans_end
);
1192 /* Deleted part of ans */
1193 if (start_offset
< ans_end
&& end_offset
> ans_start
)
1197 base.delete_range (start
, end
);
1199 state
.entered_multiply
= false;
1201 /* Update thousands separators */
1202 reformat_separators ();
1204 // FIXME: A replace will emit this both for delete-range and insert-text, can it be avoided?
1205 notify_property ("display");
1209 private class MEquation
: Equation
1211 private MathEquation m_equation
;
1213 public MEquation (MathEquation m_equation
, string equation
)
1216 this
.m_equation
= m_equation
;
1219 public override bool variable_is_defined (string name
)
1221 var lower_name
= name
.down ();
1223 if (lower_name
== "rand" || lower_name
== "ans")
1226 return m_equation
.variables
.get (name
) != null;
1229 public override Number?
get_variable (string name
)
1231 var lower_name
= name
.down ();
1233 if (lower_name
== "rand")
1234 return new Number
.random ();
1235 else if (lower_name
== "ans")
1236 return m_equation
.answer
;
1238 return m_equation
.variables
.get (name
);
1241 public override void set_variable (string name
, Number x
)
1243 /* FIXME: Don't allow writing to built-in variables, e.g. ans, rand, sin, ... */
1244 m_equation
.variables
.set (name
, x
);
1247 public override Number?
convert (Number x
, string x_units
, string z_units
)
1249 return UnitManager
.get_default ().convert_by_symbol (x
, x_units
, z_units
);