# # calculator.py : A calculator module for the deskbar applet. # # Copyright (C) 2006 by Callum McKenzie # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # Authors: Callum McKenzie # # 1.0 : Initial release. # 1.1 : Allow numbers of the style 1e6 and make the action "copy result to # the clipboard". # 1.2 : Allow basic mathematical functions and hexadecimal literals. # from gettext import gettext as _ import deskbar.Handler, deskbar.Match import deskbar.Categories import cgi import gtk from math import * # This is evil since I strongly suspect this isn't meant to be public API. # However, I can't see a better way. deskbar.Categories.CATEGORIES["calculator"] = { "name" : _("Calculator") } HANDLERS = { "CalculatorHandler" : { "name" : _("Calculator"), "description" : _("Calculate simple equations"), "version" : "1.1", } } class CalculatorMatch (deskbar.Match.Match): def get_name (self, text=None): # I really think that deskbar should be doing the # escaping for us. escapedtext = cgi.escape (text) return { "name" : self.name, "escapedtext" : escapedtext } def get_verb (self): """We print out the entire equation.""" return "%(escapedtext)s = %(name)s" def get_category (self): return "calculator" def action (self, text=None): for clipboard in ('CLIPBOARD', 'PRIMARY'): gtk.clipboard_get(clipboard).set_text(str(self.name)) class CalculatorHandler (deskbar.Handler.Handler): # This white list includes all the python mathemetical operators which # aren't comparison operators. Unfortunately >> contains >, so some slip # through. In fact the only missing operator in =. The \x00 is included # so we can tag characters to be ignored. These characters are usually # functions that have been whitelisted. whitelist = ".0123456789+-*/%<>&|^~()ex, \x00" # If a term in this list is a substring of another, it must appear # _later_ in the list (e.g. log10 and log). Also, make sure you can't # concatenate these (and e and x) to make another funcion call. whitefunctions = ( "asin", "acos", "atan2", "atan", "sinh", "cosh", "tanh", "sin", "cos", "tan", "abs", "sqrt", "pi", "log10", "log", "exp", "radians", "degrees", "ceil", "floor", "round"); def __init__ (self): deskbar.Handler.Handler.__init__ (self, "gtk-add") def query (self, query): """We evaluate the equation by first checking if all the function names are allowed, then checking that the remaining characters are in our white list and then finally we pass the result to python for evaluation. Any errors are ignored.""" try: query = query.lower() testedquery = query for func in CalculatorHandler.whitefunctions: testedquery = testedquery.replace (func, '\x00'*len(func)) for c in testedquery: if c not in CalculatorHandler.whitelist: return [] answer = eval (query) # We need this check because the eval can return function objects # when we are halfway through typing the expression. if isinstance (answer, (float, int, long)): return [ CalculatorMatch (self, name=answer)] else: return [] except: return []