diff options
author | Miguel Angel Alvarez Bernardo <miguel@miguel-ubuntu.(none)> | 2007-07-11 19:06:44 (GMT) |
---|---|---|
committer | Miguel Angel Alvarez Bernardo <miguel@miguel-ubuntu.(none)> | 2007-07-11 19:06:44 (GMT) |
commit | 60134cc71bb5aeedecbfa9aa7499016a31da0bb6 (patch) | |
tree | 0dbb32af602f036b18121f37cf1c148c40f9d015 |
Initial Commit
-rw-r--r-- | CalcActivity.py | 240 | ||||
-rw-r--r-- | CalcActivity.pyc | bin | 0 -> 9120 bytes | |||
-rw-r--r-- | MANIFEST | 2 | ||||
-rw-r--r-- | activity/activity-calc.svg | 18 | ||||
-rw-r--r-- | activity/activity.info | 7 | ||||
-rw-r--r-- | eqnparser.py | 413 | ||||
-rw-r--r-- | eqnparser.pyc | bin | 0 -> 20088 bytes | |||
-rw-r--r-- | layout.py | 137 | ||||
-rw-r--r-- | layout.pyc | bin | 0 -> 9159 bytes | |||
-rw-r--r-- | mathlib.py | 177 | ||||
-rw-r--r-- | mathlib.pyc | bin | 0 -> 9122 bytes | |||
-rw-r--r-- | setup.py | 3 | ||||
-rw-r--r-- | test.py | 44 | ||||
-rw-r--r-- | toolbars.py | 123 | ||||
-rw-r--r-- | toolbars.pyc | bin | 0 -> 10441 bytes |
15 files changed, 1164 insertions, 0 deletions
diff --git a/CalcActivity.py b/CalcActivity.py new file mode 100644 index 0000000..8a773d9 --- /dev/null +++ b/CalcActivity.py @@ -0,0 +1,240 @@ +# CalcActivity.py, sugar calculator, by Reinier Heeres <reinier@heeres.eu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +# Change log: +# 2007-07-03: rwh, first version + +import types +import os +from gettext import gettext as _ +import string + +import logging +_logger = logging.getLogger('calc-activity') + +import gobject +import pygtk +pygtk.require('2.0') +import gtk +import pango + +from sugar.activity import activity + +from layout import CalcLayout +from mathlib import MathLib +from eqnparser import EqnParser + +class CalcActivity(activity.Activity): + + TYPE_FUNCTION = 1 + TYPE_OP_PRE = 2 + TYPE_OP_POST = 3 + TYPE_TEXT = 4 + + def __init__(self, handle): + activity.Activity.__init__(self, handle) + + self.set_title("Calc2") + self.connect("key_press_event", self.keypress_cb) + self.connect("destroy", self.cleanup_cb) + + self.layout = CalcLayout(self) + self.label_entry = self.layout.label_entry + self.text_entry = self.layout.text_entry + self.history = self.layout.history + + self.reset() + + self.parser = EqnParser() + self.ml = MathLib() + + def ignore_key_cb(self, widget, event): + return True + + def cleanup_cb(self, arg): + _logger.debug('Cleaning up...') + + def process(self): + s = self.text_entry.get_text() + label = self.label_entry.get_text() + _logger.debug('process(): parsing \'%s\', label: \'%s\'', s, label) + + buf = self.history.get_buffer() + + res = self.parser.parse(s) + if res is not None: + buf.insert(buf.get_start_iter(), '\t= ' + self.ml.format_number(res) + '\n') + self.text_entry.set_text('') + self.parser.set_var('Ans', self.ml.format_number(res)) + + if len(label) > 0: + self.label_entry.set_text('') + self.parser.set_var(label, s) + + else: + pos = self.parser.get_error_offset() + self.text_entry.set_position(pos) + buf.insert(buf.get_start_iter(), '\tError at %d\n' % pos) + + if len(label) > 0: + buf.insert(buf.get_start_iter(), label + ': ' + s + '\n') + else: + buf.insert(buf.get_start_iter(), s + '\n') + + return res is not None + + def clear(self): + _logger.debug('Clearing...') + self.text_entry.set_text('') + return True + + def reset(self): + _logger.debug('Resetting...') + self.text_entry.grab_focus() + return True + +########################################## +# User interaction functions +########################################## + + def add_text(self, c): + pos = self.text_entry.get_position() + self.text_entry.insert_text(c, pos) + self.text_entry.grab_focus() + self.text_entry.set_position(pos + len(c)) + + def remove_character(self, dir): + pos = self.text_entry.get_position() + print 'Position: %d, dir: %d, len: %d' % (pos, dir, len(self.text_entry.get_text())) + if pos + dir <= len(self.text_entry.get_text()) and pos + dir >= 0: + if dir < 0: + self.text_entry.delete_text(pos+dir, pos) + else: + self.text_entry.delete_text(pos, pos+dir) + + def move_left(self): + pos = self.text_entry.get_position() + if pos > 0: + self.text_entry.set_position(pos - 1) + + def move_right(self): + pos = self.text_entry.get_position() + if pos < len(self.text_entry.get_text()): + self.text_entry.set_position(pos + 1) + + def label_entered(self): + if len(self.label_entry.get_text()) > 0: + return + pos = self.text_entry.get_position() + str = self.text_entry.get_text() + self.label_entry.set_text(str[:pos]) + self.text_entry.set_text(str[pos:]) + + def keypress_cb(self, widget, event): + if self.label_entry.is_focus(): + return + + key = gtk.gdk.keyval_name(event.keyval) + _logger.debug('Key: %s (%r)', key, event.keyval) + + allowed_chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ " + if key in allowed_chars: + self.add_text(key) + + keymap = { + 'Return': lambda: self.process(), + 'period': '.', + 'equal': '=', + 'plus': '+', + 'minus': '-', + 'asterisk': '*', + 'slash': '/', + 'BackSpace': lambda: self.remove_character(-1), + 'Delete': lambda: self.remove_character(1), + 'parenleft': '(', + 'parenright': ')', + 'exclam': '!', + 'ampersand': '&', + 'bar': '|', + 'asciicircum': '^', + 'less': '<', + 'greater': '>', + 'Left': lambda: self.move_left(), + 'Right': lambda: self.move_right(), + 'colon': lambda: self.label_entered() + } + if keymap.has_key(key): + f = keymap[key] + if type(f) is types.StringType: + self.add_text(f) + else: + return f() + + return True + + def button_pressed(self, type, str): + sel = self.text_entry.get_selection_bounds() + pos = self.text_entry.get_position() + self.text_entry.grab_focus() + if len(sel) == 2: + (start, end) = sel + text = self.text_entry.get_text() + elif len(sel) != 0: + _logger.error('button_pressed(): len(sel) != 0 or 2') + return False + + if type == self.TYPE_FUNCTION: + if sel is (): + self.text_entry.insert_text(str + '()', pos) + self.text_entry.set_position(pos + len(str) + 1) + else: + self.text_entry.set_text(text[:start] + str + '(' + text[start:end] + ')' + text[end:]) + if pos > end: + self.text_entry.set_position(pos + len(str) + 2) + elif pos > start: + self.text_entry.set_position(pos + len(str) + 1) + + elif type == self.TYPE_OP_PRE: + if len(sel) is 2: + pos = start + self.text_entry.insert_text(str, pos) + self.text_entry.set_position(pos + len(str)) + + elif type == self.TYPE_OP_POST: + if len(sel) is 2: + pos = end + self.text_entry.insert_text(str, pos) + self.text_entry.set_position(pos + len(str)) + + elif type == self.TYPE_TEXT: + if len(sel) is 2: + self.text_entry.set_text(text[:start] + str + text[end:]) + self.text_entry.set_position(pos + start - end + len(str)) + else: + self.text_entry.insert_text(str, pos) + self.text_entry.set_position(pos + len(str)) + + else: + _logger.error('CalcActivity.button_pressed(): invalid type') + +def main(): + win = gtk.Window(gtk.WINDOW_TOPLEVEL) + t = Calc(win) + gtk.main() + return 0 + +if __name__ == "__main__": + main() diff --git a/CalcActivity.pyc b/CalcActivity.pyc Binary files differnew file mode 100644 index 0000000..7bcbcca --- /dev/null +++ b/CalcActivity.pyc diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..e69644a --- /dev/null +++ b/MANIFEST @@ -0,0 +1,2 @@ +CalcActivity.py +Calc.py diff --git a/activity/activity-calc.svg b/activity/activity-calc.svg new file mode 100644 index 0000000..d338822 --- /dev/null +++ b/activity/activity-calc.svg @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ + <!ENTITY fill_color "#FFFFFF"> + <!ENTITY stroke_color "#000000"> +]> +<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50"> + <rect x="11" y="8" width="26" height="6" style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2"/> + <rect x="11" y="18" width="6" height="6" style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2"/> + <rect x="21" y="18" width="6" height="6" style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2"/> + <rect x="31" y="18" width="6" height="6" style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2"/> + <rect x="11" y="28" width="6" height="6" style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2"/> + <rect x="21" y="28" width="6" height="6" style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2"/> + <rect x="31" y="28" width="6" height="6" style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2"/> + <rect x="11" y="38" width="6" height="6" style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2"/> + <rect x="21" y="38" width="6" height="6" style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2"/> + <rect x="31" y="38" width="6" height="6" style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2"/> + +</svg> diff --git a/activity/activity.info b/activity/activity.info new file mode 100644 index 0000000..6e90e9f --- /dev/null +++ b/activity/activity.info @@ -0,0 +1,7 @@ +[Activity] +name = Calc2 +service_name = org.laptop.Calc2 +class = CalcActivity.CalcActivity +icon = activity-calc +activity_version = 1 +show_launcher = yes diff --git a/eqnparser.py b/eqnparser.py new file mode 100644 index 0000000..c2b9719 --- /dev/null +++ b/eqnparser.py @@ -0,0 +1,413 @@ +#eqnparser.py + +import logging +_logger = logging.getLogger('EqnParser') + +from mathlib import MathLib + +class Equation: + def __init__(self, eqn): + self.equation = eqn + +class ParserState: + OK = 1 + PARSE_ERROR = 2 + + def __init__(self, str): + self.str = str + self.strlen = len(str) + if self.strlen > 0: + self.char = str[0] + else: + self.char = None + self.ofs = 0 + self.level = -1 + self.result_type = EqnParser.TYPE_UNKNOWN + self.error_code = self.OK + + def state_string(self): + return 'level: %d, ofs %d' % (self.level, self.ofs) + + def more(self): + return self.error_code == self.OK and self.ofs < self.strlen + + def next(self): + self.ofs += 1 + if self.ofs < self.strlen: + self.char = self.str[self.ofs] + else: + self.char = None + return self.char + + def set_ofs(self, o): + self.ofs = o + self.char = self.str[o] + + def inc_level(self): + self.level += 1 + + def dec_level(self): + self.level -= 1 + + def set_type(self, t): + if self.result_type == EqnParser.TYPE_UNKNOWN or t is EqnParser.TYPE_SYMBOLIC: + self.result_type = t + return True + elif self.result_type != t: + _logger.debug('Type error!') + return False + else: + return True + + def set_error_code(self, c): + self.error_code = c + +class EqnParser: + OP_INVALID = -1 + OP_PRE = 1 + OP_POST = 2 + OP_DIADIC = 3 + OP_ASSIGN = 4 + + INVALID_OP = ('err', OP_INVALID, 0, lambda x: False) + + NAME_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789 ' + DIGITS = '0123456789' + SPACE_CHARS = '\t \r\n' + +# These will be filled from register_operator + OP_START_CHARS = "" + OP_CHARS = "" + + TYPE_UNKNOWN = 1 + TYPE_INT = 2 + TYPE_FLOAT = 3 + TYPE_BOOL = 4 + TYPE_SYMBOLIC = 5 + + def __init__(self): + self.ml = MathLib() + + self.error_offset = 0 + + self.variables = {} + self.functions = {} + self.operators = [] + + self.register_function('exp', 1, lambda x: self.ml.exp(x[0])) + self.register_function('ln', 1, lambda x: self.ml.ln(x[0])) + self.register_function('log10', 1, lambda x: self.ml.log10(x[0])) + self.register_function('pow', 2, lambda x: self.ml.pow(x[0], x[1])) + + self.register_function('sqrt', 1, lambda x: self.ml.sqrt(x[0])) + + self.register_function('sin', 1, lambda x: self.ml.sin(x[0])) + self.register_function('cos', 1, lambda x: self.ml.cos(x[0])) + self.register_function('tan', 1, lambda x: self.ml.tan(x[0])) + + self.register_function('asin', 1, lambda x: self.ml.asin(x[0])) + self.register_function('acos', 1, lambda x: self.ml.acos(x[0])) + self.register_function('atan', 1, lambda x: self.ml.atan(x[0])) + + self.register_function('sinh', 1, lambda x: self.ml.sinh(x[0])) + self.register_function('cosh', 1, lambda x: self.ml.cosh(x[0])) + self.register_function('tanh', 1, lambda x: self.ml.tanh(x[0])) + + self.register_function('asinh', 1, lambda x: self.ml.asinh(x[0])) + self.register_function('acosh', 1, lambda x: self.ml.acosh(x[0])) + self.register_function('atanh', 1, lambda x: self.ml.atanh(x[0])) + + self.register_operator('+', self.OP_DIADIC, 0, lambda x: self.ml.add(x[0], x[1])) + self.register_operator('+', self.OP_PRE, 1, lambda x: x[0]) + self.register_operator('-', self.OP_DIADIC, 0, lambda x: self.ml.sub(x[0], x[1])) + self.register_operator('-', self.OP_PRE, 1, lambda x: self.ml.negate(x[0])) + self.register_operator('*', self.OP_DIADIC, 1, lambda x: self.ml.mul(x[0], x[1])) + self.register_operator('/', self.OP_DIADIC, 1, lambda x: self.ml.div(x[0], x[1])) + + self.register_operator('^', self.OP_DIADIC, 2, lambda x: self.ml.pow(x[0], x[1])) + + self.register_operator('!', self.OP_POST, 0, lambda x: self.ml.factorial(x[0])) + + self.register_operator('&', self.OP_DIADIC, 0, lambda x: x[0] and x[1]) + self.register_operator('|', self.OP_DIADIC, 0, lambda x: x[0] or x[1]) + + self.register_operator('=', self.OP_DIADIC, 0, lambda x: x[0] == x[1]) + self.register_operator('!=', self.OP_DIADIC, 0, lambda x: x[0] != x[1]) + self.register_operator('<', self.OP_DIADIC, 0, lambda x: x[0] < x[1]) + self.register_operator('>', self.OP_DIADIC, 0, lambda x: x[0] > x[1]) + + self.register_operator('<<', self.OP_DIADIC, 0, lambda x: self.ml.shift_left(x[0], x[1])) + self.register_operator('>>', self.OP_DIADIC, 0, lambda x: self.ml.shift_right(x[0], x[1])) + + def register_function(self, name, nargs, f): + self.functions[name] = (nargs, f) + + def register_operator(self, op, type, presedence, f): + self.operators.append((op, type, presedence, f)) + + if op[0] not in self.OP_START_CHARS: + self.OP_START_CHARS += op[0] + for c in op: + if c not in self.OP_CHARS: + self.OP_CHARS += c + + def reset_variable_level(self, level): + return +# for i in self.variables.keys(): +# self.variables[i].highest_level = level + + def set_var(self, name, val): + self.variables[name] = val + + def lookup_var(self, name, ps): + c = self.ml.get_constant(name) + if c is not None: + return c + + if name in self.variables.keys(): +# if self.variables[name].highest_level > 0 and self.variables[name].highest_level != ps.level: +# _logger.error('EqnParser.lookup_var(): recursion detected') +# return None +# self.variables[name].highest_level = level + return self.parse(self.variables[name]) + else: + _logger.debug('variable %s not defined', name) + ps.set_type(self.TYPE_SYMBOLIC) + return None + + def eval_func(self, func, args, level): + if func not in self.functions: + _logger.error('Function \'%s\' not defined', func) + return None + + (nargs, f) = self.functions[func] + if len(args) != nargs: + _logger.error('Invalid number of arguments (%d instead of %d)', len(args), nargs) + return None + + pargs = [] + for i in range(len(args)): + pargs.append(self.parse(args[i])) + if pargs[i] is None: + _logger.error('Unable to parse argument %d: \'%s\'', i, args[i]) + return None + + res = f(pargs) + _logger.debug('Function \'%s\' returned %s', func, self.ml.format_number(res)) + return res + + def parse_number(self, ps): + startofs = ps.ofs + +# integer part + while ps.more() and ps.char in self.DIGITS: + ps.next() + +# part after dot + if ps.char == '.': + ps.next() + while ps.more() and ps.char in self.DIGITS: + ps.next() + +# exponent + if ps.char is not None and ps.char in 'eE': + ps.next() + if ps.char is not None and ps.char in '+-': + ps.next() + while ps.more() and ps.char in self.DIGITS: + ps.next() + + _logger.debug('parse_number(): %d - %d: %s', startofs, ps.ofs, ps.str[startofs:ps.ofs]) + n = self.ml.parse_number(ps.str[startofs:ps.ofs]) + return n + + def valid_operator(self, opstr, left_val): + for op_tuple in self.operators: + (op, type, presedence, f) = op_tuple + if op == opstr: + if type == self.OP_DIADIC and left_val is not None: + return op_tuple + elif type == self.OP_POST and left_val is not None: + return op_tuple + elif type == self.OP_PRE and left_val is None: + return op_tuple + return None + + def parse_operator(self, ps, left_val): + startofs = ps.ofs + while ps.more() and ps.char in self.OP_CHARS: + ps.next() + op = self.valid_operator(ps.str[startofs:ps.ofs], left_val) + if op is not None: + _logger.debug('parse_operator(): %d - %d: %s', startofs, ps.ofs, ps.str[startofs:ps.ofs]) + return op + + return self.INVALID_OP + + def parse_func_args(self, ps): + startofs = ps.ofs + args = [] + pcount = 1 + while ps.more() and pcount > 0: + if ps.char == ',': + args.append(ps.str[startofs:ps.ofs]) + startofs = ps.ofs + 1 + elif ps.char == '(': + pcount += 1 + elif ps.char == ')': + pcount -= 1 + if pcount == 0: + args.append(ps.str[startofs:ps.ofs]) + ps.next() + _logger.debug('parse_func_args(): %d - %d: %r', startofs, ps.ofs, args) + return args + + def parse_var_func(self, ps): + startofs = ps.ofs + while ps.more() and ps.char in self.NAME_CHARS: + ps.next() + name = ps.str[startofs:ps.ofs] + name.strip(self.SPACE_CHARS) + name.rstrip(self.SPACE_CHARS) + +# handle function + if ps.char == '(': + ps.next() + _logger.debug('parse_var_func(): function %d - %d: %s', startofs, ps.ofs, name) + args = self.parse_func_args(ps) + return self.eval_func(name, args, ps.level) + +# handle var + else: + _logger.debug('parse_var_func(): variable %d - %d: %s', startofs, ps.ofs, name) + res = self.lookup_var(name, ps) + if res is None: + ps.set_ofs(startofs) + ps.set_error_code(ParserState.PARSE_ERROR) + return res + + def _parse(self, ps, presedence=None): + if presedence is None: + ps.inc_level() + _logger.debug('_parse(): %s, presedence: %r', ps.state_string(), presedence) + + left_val = None + right_val = None + op = None + while ps.more(): +# _logger.debug('Looking at \'%c\', ofs %d in \'%s\'', ps.char, ps.ofs, ps.str) + +# Skip spaces + if ps.char in self.SPACE_CHARS: + ps.next() + +# Left parenthesis: parse sub-expression + elif ps.char == '(': + ps.next() + left_val = self._parse(ps) + +# Right parenthesis: return from parsing sub-expression +# -If we are looking ahead because of operator presedence return value +# -Else move to next character, decrease level and return value + elif ps.char == ')': + if presedence is not None: + if left_val is None: + _logger.error('Parse error (right parenthesis)') + ps.set_error_code(ParserState.PARSE_ERROR) + return None + else: + _logger.debug('returning %s', self.ml.format_number(left_val)) + return left_val + + if ps.level > 0: + ps.next() + ps.dec_level() + if left_val is None: + _logger.error('Parse error (right parenthesis, no left_val)') + ps.set_error_code(ParserState.PARSE_ERROR) + return None + else: + _logger.debug('returning %s', self.ml.format_number(left_val)) + return left_val + else: + _logger.error('Parse error (right parenthesis, no level to close)') + ps.set_error_code(ParserState.PARSE_ERROR) + return None + +# Parse number + elif ps.char in '0123456789.': + if right_val is not None or left_val is not None: + _logger.error('Number not expected!') + ps.set_error_code(ParserState.PARSE_ERROR) + return None + + if op is not None and otype == self.OP_PRE: + left_val = of([self.parse_number(ps)]) + op = None + else: + left_val = self.parse_number(ps) + +# Parse operator + elif ps.char in self.OP_START_CHARS: + startofs = ps.ofs + op = self.parse_operator(ps, left_val) + (opstr, otype, opres, of) = op + +# Diadic operators + if otype == self.OP_DIADIC: + if presedence is not None and opres <= presedence: + ps.set_ofs(startofs) + _logger.debug('returning %s (by presedence, %d)', self.ml.format_number(left_val), ps.ofs) + return left_val + else: + right_val = self._parse(ps, presedence=opres) + if right_val == None: + return None + + res = of([left_val, right_val]) + _logger.debug('OP: %s, %s ==> %s', self.ml.format_number(left_val), self.ml.format_number(right_val), self.ml.format_number(res)) + left_val = res + right_val = None + + op = None + +# Operator that goes after value + elif otype == self.OP_POST: + left_val = of([left_val]) + op = None + +# Operator that goes before value + elif otype == self.OP_PRE: + pass + + elif otype == self.OP_INVALID: + _logger.debug('Invalid operator') + +# Parse variable or function + else: + left_val = self.parse_var_func(ps) + + if op is None and left_val is not None: + _logger.debug('returning %s', self.ml.format_number(left_val)) + return left_val + else: + _logger.error('_parse(): returning None') + ps.set_error_code(ParserState.PARSE_ERROR) + return None + + def set_error_offset(self, o): + self.error_offset = o + + def get_error_offset(self): + return self.error_offset + + def parse(self, eqn): + """Construct ParserState object and call _parse""" + _logger.debug('parse(): %s', eqn) + self.reset_variable_level(0) + ps = ParserState(eqn) + res = self._parse(ps) + if res is None: + self.set_error_offset(ps.ofs) + return res + diff --git a/eqnparser.pyc b/eqnparser.pyc Binary files differnew file mode 100644 index 0000000..2d642ad --- /dev/null +++ b/eqnparser.pyc diff --git a/layout.py b/layout.py new file mode 100644 index 0000000..b32266d --- /dev/null +++ b/layout.py @@ -0,0 +1,137 @@ +from gettext import gettext as _ +import pygtk +pygtk.require('2.0') +import gtk +import pango + +from sugar.activity import activity + +from toolbars import * + +class CalcLayout: + + def __init__(self, parent): + self._parent = parent + self.create_dialog() + + def create_color(self, rf, gf, bf): + return gtk.gdk.Color(int(rf*0xFFFF), int(gf*0xFFFF), int(bf*0xFFFF)) + + def create_button_data(self): + self.button_data = [ +# [x, y, width, label, bgcol, cb] + [1, 1, 1, '7', self.col_gray2, lambda w: self._parent.add_text('7')], + [2, 1, 1, '8', self.col_gray2, lambda w: self._parent.add_text('8')], + [3, 1, 1, '9', self.col_gray2, lambda w: self._parent.add_text('9')], + + [1, 2, 1, '4', self.col_gray2, lambda w: self._parent.add_text('4')], + [2, 2, 1, '5', self.col_gray2, lambda w: self._parent.add_text('5')], + [3, 2, 1, '6', self.col_gray2, lambda w: self._parent.add_text('6')], + + [1, 3, 1, '1', self.col_gray2, lambda w: self._parent.add_text('1')], + [2, 3, 1, '2', self.col_gray2, lambda w: self._parent.add_text('2')], + [3, 3, 1, '3', self.col_gray2, lambda w: self._parent.add_text('3')], + + [1, 4, 1, '0', self.col_gray2, lambda w: self._parent.add_text('0')], + [2, 4, 1, '.', self.col_gray2, lambda w: self._parent.add_text('.')], + [3, 4, 1, 'Ans', self.col_gray2, lambda w: self._parent.add_text('Ans')], + + [4, 1, 3, 'clear', self.col_gray1, lambda w: self._parent.clear()], + + [4, 2, 1, '+', self.col_gray3, lambda w: self._parent.add_character('+')], + [5, 2, 1, '-', self.col_gray3, lambda w: self._parent.add_character('-')], + [6, 2, 1, '(', self.col_gray3, lambda w: self._parent.add_character('(')], + [4, 3, 1, 'x', self.col_gray3, lambda w: self._parent.add_character('*')], + [5, 3, 1, '/', self.col_gray3, lambda w: self._parent.add_character('/')], + [6, 3, 1, ')', self.col_gray3, lambda w: self._parent.add_character(')')], + + [4, 4, 3, 'enter', self.col_gray1, lambda w: self._parent.process()], + ] + + def create_dialog(self): +# Toolbar + toolbox = activity.ActivityToolbox(self._parent) + self._parent.set_toolbox(toolbox) + toolbox.add_toolbar(_('Edit'), EditToolbar(self._parent)) + toolbox.add_toolbar(_('Algebra'), AlgebraToolbar(self._parent)) + toolbox.add_toolbar(_('Trigonometry'), TrigonometryToolbar(self._parent)) + toolbox.add_toolbar(_('Boolean'), BooleanToolbar(self._parent)) + toolbox.add_toolbar(_('Constants'), ConstantsToolbar(self._parent)) + toolbox.add_toolbar(_('Format'), FormatToolbar(self._parent)) + toolbox.show_all() + +# Some layout constants + self.font = pango.FontDescription(str='sans bold 15') + self.col_white = self.create_color(1.00, 1.00, 1.00) + self.col_gray1 = self.create_color(0.69, 0.71, 0.72) + self.col_gray2 = self.create_color(0.51, 0.51, 0.53) + self.col_gray3 = self.create_color(0.30, 0.30, 0.31) + self.col_black = self.create_color(0.00, 0.00, 0.00) + +# Container + hc1 = gtk.HBox(False, 10) + hc1.set_border_width(10) + if issubclass(type(self._parent), gtk.Bin) and self._parent.get_child() is not None: + self._parent.get_child().add(hc1) + else: + self._parent.add(hc1) + +# Left part: container and input + vc1 = gtk.VBox(False, 10) + hc1.add(vc1) + hc2 = gtk.HBox(False, 10) + vc1.add(hc2) + label1 = gtk.Label(_('Label:')) + hc2.add(label1) + self.label_entry = gtk.Entry() + hc2.add(self.label_entry) + self.text_entry = gtk.Entry() + vc1.add(self.text_entry) + self.text_entry.set_size_request(400, 100) + self.text_entry.connect('key_press_event', self._parent.ignore_key_cb) + self.text_entry.modify_font(self.font) + +# Left part: buttons + self.pad = gtk.Table(4, 6, True) + vc1.add(self.pad) + self.pad.set_row_spacings(6) + self.pad.set_col_spacings(6) + + self.create_button_data() + self.buttons = [] + for i in range(len(self.button_data)): + x, y, w, cap, bgcol, cb = self.button_data[i] + button = self.create_button(_(cap), cb, self.col_white, bgcol, w) + self.buttons.append(button) + self.pad.attach(button, x, x+w, y, y+1) + +# Right part: container and equation button + vc2 = gtk.VBox(10) + hc1.add(vc2) + eqbut = gtk.Button('All equations') + vc2.add(eqbut) + +# Right part: last equation + +# Right part: history + self.history = gtk.TextView() + vc2.add(self.history) + self.history.set_size_request(300, 400) + self.history.set_editable(False) + self.history.set_cursor_visible(False) + + self._parent.show_all() + + def create_button(self, cap, cb, fgcol, bgcol, width): + button = gtk.Button(cap) + self.modify_button_appearance(button, fgcol, bgcol, width) + button.connect("clicked", cb) + button.connect("key_press_event", self._parent.ignore_key_cb) + return button + + def modify_button_appearance(self, button, fgcol, bgcol, width): + width = 50 * width + button.get_child().set_size_request(width, 50) + button.get_child().modify_font(self.font) + button.get_child().modify_fg(gtk.STATE_NORMAL, fgcol) + button.modify_bg(gtk.STATE_NORMAL, bgcol) diff --git a/layout.pyc b/layout.pyc Binary files differnew file mode 100644 index 0000000..28fc3c2 --- /dev/null +++ b/layout.pyc diff --git a/mathlib.py b/mathlib.py new file mode 100644 index 0000000..7464552 --- /dev/null +++ b/mathlib.py @@ -0,0 +1,177 @@ +#MathLib.py + +import types +import math +from decimal import Decimal + +class MathLib: + def __init__(self): + self.constants = {} + self.set_constant('true', True) + self.set_constant('false', False) + self.set_constant('pi', self.parse_number('3.14')) + self.set_constant('kb', self.parse_number('0')) + self.set_constant('Na', self.parse_number('6.02214e23')) + self.set_constant('e', self.exp(1)) + self.set_constant('c', self.parse_number('3e8')) + self.set_constant('c_e', self.parse_number('0')) #electron properties + self.set_constant('m_e', self.parse_number('0')) + self.set_constant('c_p', self.parse_number('0')) #proton properties + self.set_constant('m_p', self.parse_number('0')) + self.set_constant('c_n', self.parse_number('0')) #neutron properties + self.set_constant('m_n', self.parse_number('0')) + + def set_constant(self, name, val): + self.constants[name] = val + + def get_constant(self, name): + if name in self.constants: + return self.constants[name] + else: + return None + + def d(self, val): + s = '%e' % val + return Decimal(s) + + def parse_number(self, s): + return Decimal(s) + + def format_number(self, n): + if type(n) is types.BooleanType: + if n: + return 'True' + else: + return 'False' + + (sign, digits, exp) = n.as_tuple() + if sign == '-': + res = "-" + else: + res = "" + int_len = len(digits) + exp + disp_exp = math.floor(int_len / 3) * 3 + if disp_exp == 3: + disp_exp = 0 + dot_pos = int_len - disp_exp + for i in xrange(len(digits)): + if i == dot_pos: + if i == 0: + res += '0.' + else: + res += '.' + res += str(digits[i]) + if disp_exp != 0: + res = res + 'e%d' % disp_exp + + return res + + def is_int(self, n): + (sign, d, e) = n.normalize().as_tuple() + return e == 0 + + def is_float(self, n): + if isinstance(n, Decimal): + return not self.is_int(n) + else: + return False + + def is_bool(self, n): + return type(n) is types.BoolType + + def compare(self, x, y): + return x == y + + def negate(self, x): + return -x + + def abs(self, x): + return self.d(math.fabs(x)) + + def add(self, x, y): + return x + y + + def sub(self, x, y): + return x - y + + def mul(self, x, y): + return x * y + + def div(self, x, y): + return x / y + + def pow(self, x, y): + if self.is_int(y): + return x ** y + else: + return self.d(math.pow(x, y)) + + def sqrt(self, x): + return self.d(math.sqrt(x)) + + def mod(self, x, y): + if self.is_int(y): + return x % y + else: + return self.d(0) + + def exp(self, x): + return self.d(math.exp(x)) + + def ln(self, x): + if x > 0: + return self.d(math.log(x)) + else: + return 0 + + def log10(self, x): + if x > 0: + return self.d(math.log10(x)) + else: + return 0 + + def factorial(self, n): + if not self.is_int(n): + return self.d(0) + + res = n + while n > 2: + res *= n - 1 + n -= 1 + return res + + def sin(self, x): + return self.d(math.sin(x)) + + def cos(self, x): + return self.d(math.cos(x)) + + def tan(self, x): + return self.d(math.tan(x)) + + def asin(self, x): + return self.d(math.asin(x)) + + def acos(self, x): + return self.d(math.acos(x)) + + def atan(self, x): + return self.d(math.atan(x)) + + def sinh(self, x): + return self.d(math.sinh(x)) + + def cosh(self, x): + return self.d(math.cosh(x)) + + def tanh(self, x): + return self.d(math.tanh(x)) + + def asinh(self, x): + return self.d(math.asinh(x)) + + def acosh(self, x): + return self.d(math.acosh(x)) + + def atanh(self, x): + return self.d(math.atanh(x)) diff --git a/mathlib.pyc b/mathlib.pyc Binary files differnew file mode 100644 index 0000000..69ddc6f --- /dev/null +++ b/mathlib.pyc diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..ec0f64e --- /dev/null +++ b/setup.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from sugar.activity import bundlebuilder +bundlebuilder.start() @@ -0,0 +1,44 @@ +import logging +logging.basicConfig(level=logging.DEBUG, + filename='/home/rwh/Calc2.test.log', + filemode='w') + +from eqnparser import EqnParser +from mathlib import MathLib +import decimal + +ml = MathLib() +a = EqnParser() +eq = '2.0 * 1.23e-17 / sin((2+3)*3) * 2^7.5*1.1' +#eq = "(2+3)*3" +res = a.parse(eq) +print 'Eq: \'%s\' ==> %s' % (eq, ml.format_number(res)) + +a = decimal.Decimal('1234567.89') +print ml.format_number(a) +a = decimal.Decimal('123456.789') +print ml.format_number(a) +a = decimal.Decimal('12345.6789') +print ml.format_number(a) +a = decimal.Decimal('1234.56789') +print ml.format_number(a) +a = decimal.Decimal('123.456789') +print ml.format_number(a) +a = decimal.Decimal('12.3456789') +print ml.format_number(a) +a = decimal.Decimal('1.23456789') +print ml.format_number(a) +a = decimal.Decimal('0.123456789') +print ml.format_number(a) +a = decimal.Decimal('0.0123456789') +print ml.format_number(a) +a = decimal.Decimal('0.00123456789') +print ml.format_number(a) +a = decimal.Decimal('0.000123456789') +print ml.format_number(a) +a = decimal.Decimal('0.0000123456789') +print ml.format_number(a) +a = decimal.Decimal('0.00000123456789') +print ml.format_number(a) +a = decimal.Decimal('0.000000123456789') +print ml.format_number(a) diff --git a/toolbars.py b/toolbars.py new file mode 100644 index 0000000..bb70af2 --- /dev/null +++ b/toolbars.py @@ -0,0 +1,123 @@ +import pygtk +pygtk.require('2.0') +import gtk + +from sugar.graphics.toolbutton import ToolButton +from sugar.graphics.toggletoolbutton import ToggleToolButton + +class TextToolButton(gtk.ToolButton): + def __init__(self, text, cb): + gtk.ToolButton.__init__(self) + self.set_label(text) + self.connect('clicked', cb) + +class IconToolButton(ToolButton): + def __init__(self, text, cb): + ToolButton.__init__(self, text) + self.connect('clicked', cb) + +class TextToggleToolButton(gtk.ToggleToolButton): + def __init__(self, text, cb): + gtk.ToggleToolButton.__init__(self) + self.set_label(text) + self.connect('clicked', cb) + +class IconToggleToolButton(ToggleToolButton): + def __init__(self, text, cb): + ToggleToolButton.__init__(self, text) + self.connect('clicked', cb) + +class LineSeparator(gtk.SeparatorToolItem): + def __init__(self): + gtk.SeparatorToolItem.__init__(self) + self.set_draw(True) + +class EditToolbar(gtk.Toolbar): + def __init__(self, calc): + gtk.Toolbar.__init__(self) + +class AlgebraToolbar(gtk.Toolbar): + def __init__(self, calc): + gtk.Toolbar.__init__(self) + + self.insert(TextToolButton('square', + lambda x: calc.button_pressed(calc.TYPE_OP_POST, '^2')), -1) + + self.insert(TextToolButton('sqrt', + lambda x: calc.button_pressed(calc.TYPE_FUNCTION, 'sqrt')), -1) + + self.insert(LineSeparator(), -1) + + self.insert(TextToolButton('exp', + lambda x: calc.button_pressed(calc.TYPE_OP_POST, 'exp')), -1) + + self.insert(TextToolButton('ln', + lambda x: calc.button_pressed(calc.TYPE_FUNCTION, 'ln')), -1) + + self.insert(LineSeparator(), -1) + + self.insert(TextToolButton('fac', + lambda x: calc.button_pressed(calc.TYPE_OP_POST, '!')), -1) + +class TrigonometryToolbar(gtk.Toolbar): + def __init__(self, calc): + gtk.Toolbar.__init__(self) + + self.insert(TextToolButton('sin', + lambda x: calc.button_pressed(calc.TYPE_FUNCTION, 'sin')), -1) + + self.insert(TextToolButton('cos', + lambda x: calc.button_pressed(calc.TYPE_FUNCTION, 'cos')), -1) + + self.insert(TextToolButton('tan', + lambda x: calc.button_pressed(calc.TYPE_FUNCTION, 'tan')), -1) + + self.insert(LineSeparator(), -1) + + self.insert(TextToolButton('sinh', + lambda x: calc.button_pressed(calc.TYPE_FUNCTION, 'sinh')), -1) + + self.insert(TextToolButton('cosh', + lambda x: calc.button_pressed(calc.TYPE_FUNCTION, 'cosh')), -1) + + self.insert(TextToolButton('tanh', + lambda x: calc.button_pressed(calc.TYPE_FUNCTION, 'tanh')), -1) + +class BooleanToolbar(gtk.Toolbar): + def __init__(self, calc): + gtk.Toolbar.__init__(self) + + self.insert(TextToolButton('and', + lambda x: calc.button_pressed(calc.TYPE_OP_POST, '&')), -1) + + self.insert(TextToolButton('or', + lambda x: calc.button_pressed(calc.TYPE_OP_POST, '|')), -1) + + self.insert(TextToolButton('xor', + lambda x: calc.button_pressed(calc.TYPE_OP_POST, '^')), -1) + + self.insert(LineSeparator(), -1) + + self.insert(TextToolButton('eq', + lambda x: calc.button_pressed(calc.TYPE_OP_POST, '=')), -1) + + self.insert(TextToolButton('neq', + lambda x: calc.button_pressed(calc.TYPE_OP_POST, '!=')), -1) + +class ConstantsToolbar(gtk.Toolbar): + def __init__(self, calc): + gtk.Toolbar.__init__(self) + + self.insert(TextToolButton('pi', + lambda x: calc.button_pressed(calc.TYPE_TEXT, 'pi')), -1) + + self.insert(TextToolButton('e', + lambda x: calc.button_pressed(calc.TYPE_TEXT, 'e')), -1) + +class FormatToolbar(gtk.Toolbar): + def __init__(self, calc): + gtk.Toolbar.__init__(self) + + self.insert(TextToggleToolButton('rad/deg', + lambda x: True), -1) + diff --git a/toolbars.pyc b/toolbars.pyc Binary files differnew file mode 100644 index 0000000..711c541 --- /dev/null +++ b/toolbars.pyc |