Updated kn translations
[gcalctool.git] / src / math-equation.vala
blob5163909ff9ff8518d49b522457b40d3a857a0cde
1 /*
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
9 * license.
12 public enum NumberMode
14 NORMAL,
15 SUPERSCRIPT,
16 SUBSCRIPT
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;
39 public string error;
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;
50 public int word_size
52 get { return _word_size; }
53 set
55 if (_word_size == value)
56 return;
57 _word_size = value;
58 notify_property ("word-size");
62 private string _source_currency;
63 public string source_currency
65 owned get { return _source_currency; }
66 set
68 if (_source_currency == value)
69 return;
70 _source_currency = value;
74 private string _target_currency;
75 public string target_currency
77 owned get { return _target_currency; }
78 set
80 if (_target_currency == value)
81 return;
82 _target_currency = value;
86 private string _source_units;
87 public string source_units
89 owned get { return _source_units; }
90 set
92 if (_source_units == value)
93 return;
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)
105 return;
106 _target_units = value;
110 public string display
112 owned get
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;
139 public 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];
177 else
178 digits[i] = ds[i].get_char (0);
181 _variables = new MathVariables ();
183 state = new MathEquationState ();
184 state.status = "";
185 word_size = 32;
186 _angle_units = AngleUnit.DEGREES;
187 // FIXME: Pick based on locale
188 source_currency = "";
189 target_currency = "";
190 source_units = "";
191 target_units = "";
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)
204 start = -1;
205 end = -1;
206 return;
209 Gtk.TextIter iter;
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)
219 return;
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;
230 in_reformat = 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);
242 in_reformat = false;
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;
257 in_reformat = true;
259 var text = display;
260 int ans_start, ans_end;
261 get_ans_offsets (out ans_start, out ans_end);
262 var offset = -1;
263 var index = 0;
264 unichar c;
265 while (text.get_next_char (ref index, out c))
267 offset++;
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;
279 continue;
281 if (c.isdigit ())
283 if (!in_number)
284 digit_offset = count_digits (text, index) + 1;
285 in_number = true;
287 /* Expected a thousands separator between these digits - insert it */
288 if (expect_tsep)
290 Gtk.TextIter iter;
291 get_iter_at_offset (out iter, offset);
292 (this as Gtk.TextBuffer).insert (ref iter, serializer.get_thousands_separator ().to_string (), -1);
293 offset++;
294 last_is_tsep = true;
297 digit_offset--;
299 else if (c == serializer.get_radix ())
301 in_number = true;
302 in_radix = true;
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);
313 offset--;
315 else
316 last_is_tsep = true;
318 else
320 in_number = false;
321 in_radix = false;
325 in_reformat = false;
326 in_undo_operation = false;
329 private int count_digits (string text, int index)
331 var count = 0;
332 var following_separator = false;
333 unichar c;
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)
340 return count;
341 following_separator = true;
343 else if (c.isdigit ())
345 following_separator = false;
346 count++;
348 else
349 return count;
352 return count;
355 private void reformat_display ()
357 /* Change ans */
358 reformat_ans ();
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)
370 Gtk.TextIter iter;
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 ();
378 s.ans = state.ans;
379 s.expression = display;
380 s.ans_start = ans_start;
381 s.ans_end = ans_end;
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;
388 return s;
391 private void push_undo_stack ()
393 if (in_undo_operation)
394 return;
396 status = "";
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)
408 return;
410 if (do_remove_tag)
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;
421 ans_end_mark = null;
424 private void apply_state (MathEquationState s)
426 /* Disable undo detection */
427 in_undo_operation = true;
429 state.ans = s.ans;
430 set_text (s.expression, -1);
431 Gtk.TextIter cursor;
432 get_iter_at_offset (out cursor, s.cursor);
433 place_cursor (cursor);
434 clear_ans (false);
435 if (s.ans_start >= 0)
437 Gtk.TextIter start;
438 get_iter_at_offset (out start, s.ans_start);
439 ans_start_mark = create_mark (null, start, false);
440 Gtk.TextIter end;
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;
449 status = s.status;
451 in_undo_operation = false;
454 public void copy ()
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);
464 public void paste ()
466 Gtk.Clipboard.get (Gdk.Atom.NONE).request_text (on_paste);
469 private void on_paste (Gtk.Clipboard clipboard, string? text)
471 if (text != null)
472 insert (text);
475 public void undo ()
477 if (undo_stack == null)
479 /* Error shown when trying to undo with no undo history */
480 status = _("No undo history");
481 return;
484 state = undo_stack.nth_data (0);
485 undo_stack.remove (state);
486 redo_stack.prepend (get_current_state ());
488 apply_state (state);
491 public void redo ()
493 if (redo_stack == null)
495 /* Error shown when trying to redo with no redo history */
496 status = _("No redo history");
497 return;
500 state = redo_stack.nth_data (0);
501 redo_stack.remove (state);
502 undo_stack.prepend (get_current_state ());
504 apply_state (state);
507 public unichar get_digit_text (uint digit)
509 if (digit >= 16)
510 return '?';
511 return digits[digit];
514 public int accuracy
516 get { return serializer.get_trailing_digits (); }
519 if (serializer.get_trailing_digits () == value)
520 return;
521 serializer.set_trailing_digits (value);
522 reformat_display ();
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)
533 return;
535 serializer.set_show_thousands_separators (value);
536 reformat_display ();
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)
547 return;
549 serializer.set_show_trailing_zeroes (value);
550 reformat_display ();
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)
561 return;
563 serializer.set_number_format (value);
564 reformat_display ();
565 notify_property ("number-format");
569 public int number_base
571 get { return serializer.get_base (); }
574 if (serializer.get_base () == value)
575 return;
577 serializer.set_base (value);
578 reformat_display ();
579 notify_property ("number-base");
583 public AngleUnit angle_units
585 get { return _angle_units; }
588 if (_angle_units == value)
589 return;
591 _angle_units = value;
592 notify_property ("angle-units");
596 public string status
598 owned get { return state.status; }
601 if (state.status == value)
602 return;
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;
639 public bool is_empty
641 get { return get_char_count () == 0; }
644 public bool is_result
646 get { return equation == "ans"; }
649 public string equation
651 owned get
653 var text = display;
654 var eq_text = "";
656 var ans_start = -1, ans_end = -1;
657 if (ans_start_mark != null)
658 get_ans_offsets (out ans_start, out ans_end);
659 if (ans_start >= 0)
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;
663 var index = 0;
664 unichar c;
665 while (text.get_next_char (ref index, out c))
667 var is_digit = c.isdigit ();
668 var next_is_digit = false;
669 unichar next_char;
670 var i = index;
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))
679 eq_text += ".";
680 else
681 eq_text += c.to_string ();
683 last_is_digit = is_digit;
686 return eq_text;
690 public Number? number
692 owned get
694 if (is_result)
695 return answer;
696 else
697 return serializer.from_string (equation);
701 public NumberMode number_mode
703 get { return _number_mode; }
706 if (_number_mode == value)
707 return;
709 can_super_minus = value == NumberMode.SUPERSCRIPT;
711 _number_mode = value;
712 notify_property ("number-mode");
716 public Number answer
718 get { return state.ans; }
721 public void store (string name)
723 var t = number;
724 if (t == null)
725 status = _("No sane value to store");
726 else
727 variables.set (name, t);
730 public void recall (string name)
732 insert (name);
735 public new void set (string text)
737 set_text (text, -1);
738 clear_ans (false);
741 public void set_number (Number x)
743 /* Show the number in the user chosen format */
744 var text = serializer.to_string (x);
745 set_text (text, -1);
746 state.ans = x;
748 /* Mark this text as the answer variable */
749 Gtk.TextIter start, end;
750 get_bounds (out start, out end);
751 clear_ans (false);
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)
762 Gtk.TextIter iter;
763 get_iter_at_mark (out iter, get_insert ());
764 (this as Gtk.TextBuffer).backspace (iter, true, true);
765 insert_at_cursor ("^", -1);
766 return;
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[] = {'⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'};
786 if (digit >= 16)
787 return;
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 ()
809 insert ("×10");
810 number_mode = NumberMode.SUPERSCRIPT;
813 public void insert_subtract ()
815 if (number_mode == NumberMode.SUPERSCRIPT && can_super_minus)
817 insert ("⁻");
818 can_super_minus = false;
820 else
822 insert ("−");
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 ();
845 var text = equation;
846 /* Count the number of brackets and automatically add missing closing brackets */
847 var n_brackets = 0;
848 for (var i = 0; text[i] != '\0'; i++)
850 if (text[i] == '(')
851 n_brackets++;
852 else if (text[i] == ')')
853 n_brackets--;
855 while (n_brackets > 0)
857 text += ")";
858 n_brackets--;
861 ErrorCode error_code;
862 string error_token;
863 uint error_start, error_end;
864 var z = parse (text, out error_code, out error_token, out error_start, out error_end);
865 switch (error_code)
867 case ErrorCode.NONE:
868 solvedata.number_result = z;
869 break;
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");
874 break;
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;
881 break;
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;
888 break;
890 case ErrorCode.UNKNOWN_CONVERSION:
891 solvedata.error = /* Error displayed to user when an conversion with unknown units is attempted */
892 _("Unknown conversion");
893 break;
895 case ErrorCode.MP:
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");
906 break;
908 default:
909 solvedata.error = /* Error displayed to user when they enter an invalid calculation */
910 _("Malformed expression");
911 break;
913 queue.push (solvedata);
915 return null;
918 private bool show_in_progress ()
920 if (in_solve)
921 status = _("Calculating");
922 return false;
925 private bool look_for_answer ()
927 var result = queue.try_pop ();
929 if (result == null)
930 return true;
932 _in_solve = false;
934 if (result.error == null)
935 status = "";
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);
954 return false;
957 public void solve ()
959 // FIXME: should replace calculation or give error message
960 if (in_solve)
961 return;
963 if (is_empty)
964 return;
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")
968 if (is_result)
970 undo ();
971 return;
974 _in_solve = true;
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 ()
987 Gtk.TextIter start;
988 get_start_iter (out start);
989 var temp = start;
990 var end = 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. */
1008 temp = start;
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 ()
1018 var x = number;
1019 var factors = x.factorize ();
1021 var text = "";
1022 var i = 0;
1023 foreach (var factor in factors)
1025 if (i != 0)
1026 text += "×";
1027 text += serializer.to_string (factor);
1028 i++;
1031 var result = new SolveData ();
1032 result.text_result = text;
1033 queue.push (result);
1035 return null;
1038 public void factorize ()
1040 // FIXME: should replace calculation or give error message
1041 if (in_solve)
1042 return;
1044 var x = number;
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");
1049 return;
1052 _in_solve = true;
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 ()
1062 int cursor;
1063 get ("cursor-position", out cursor, null);
1064 if (cursor >= get_char_count ())
1065 return;
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 */
1076 if (is_empty)
1077 return;
1079 if (has_selection)
1080 delete_selection (false, false);
1081 else
1083 Gtk.TextIter iter;
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;
1092 set_text ("", -1);
1093 clear_ans (false);
1096 public void shift (int count)
1098 var z = number;
1099 if (z == null)
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");
1103 return;
1106 set_number (z.shift (count));
1109 public void toggle_bit (uint bit)
1111 var x = number;
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");
1117 return;
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
1125 set_number (x);
1128 protected override void insert_text (ref Gtk.TextIter location, string text, int len)
1130 if (in_reformat)
1132 base.insert_text (ref location, text, len);
1133 return;
1136 /* If following a delete then have already pushed undo stack (Gtk.TextBuffer doesn't indicate replace operations so we have to infer them) */
1137 if (!in_delete)
1138 push_undo_stack ();
1140 /* Clear result on next digit entered if cursor at end of line */
1141 var c = text.get_char (0);
1142 int cursor;
1143 get ("cursor-position", out cursor, null);
1144 if ((c.isdigit () || c == serializer.get_radix ()) && is_result && cursor >= get_char_count ())
1146 set_text ("", -1);
1147 clear_ans (false);
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)
1159 clear_ans (true);
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)
1174 if (in_reformat)
1176 base.delete_range (start, end);
1177 return;
1180 push_undo_stack ();
1182 in_delete = true;
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)
1194 clear_ans (true);
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)
1215 base (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")
1224 return true;
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;
1237 else
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);