Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMiguel 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)
commit60134cc71bb5aeedecbfa9aa7499016a31da0bb6 (patch)
tree0dbb32af602f036b18121f37cf1c148c40f9d015
Initial Commit
-rw-r--r--CalcActivity.py240
-rw-r--r--CalcActivity.pycbin0 -> 9120 bytes
-rw-r--r--MANIFEST2
-rw-r--r--activity/activity-calc.svg18
-rw-r--r--activity/activity.info7
-rw-r--r--eqnparser.py413
-rw-r--r--eqnparser.pycbin0 -> 20088 bytes
-rw-r--r--layout.py137
-rw-r--r--layout.pycbin0 -> 9159 bytes
-rw-r--r--mathlib.py177
-rw-r--r--mathlib.pycbin0 -> 9122 bytes
-rw-r--r--setup.py3
-rw-r--r--test.py44
-rw-r--r--toolbars.py123
-rw-r--r--toolbars.pycbin0 -> 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
new file mode 100644
index 0000000..7bcbcca
--- /dev/null
+++ b/CalcActivity.pyc
Binary files differ
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
new file mode 100644
index 0000000..2d642ad
--- /dev/null
+++ b/eqnparser.pyc
Binary files differ
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
new file mode 100644
index 0000000..28fc3c2
--- /dev/null
+++ b/layout.pyc
Binary files differ
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
new file mode 100644
index 0000000..69ddc6f
--- /dev/null
+++ b/mathlib.pyc
Binary files differ
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()
diff --git a/test.py b/test.py
new file mode 100644
index 0000000..37ac95e
--- /dev/null
+++ b/test.py
@@ -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
new file mode 100644
index 0000000..711c541
--- /dev/null
+++ b/toolbars.pyc
Binary files differ