#
# calculator.py : A calculator module for the deskbar applet.
#
# Copyright (C) 2007 by Michael Hofmann
# 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 - Original author
# Michael Hofmann - compatibility changes for deskbar 2.20
#
from __future__ import division
# Developers:
# Enable this to see what goes wrong when evaluating the string.
# Since half-written strings get evaluated, this produces a lot
# of crap as well as the answer you want.
_debug = False
from gettext import gettext as _
import deskbar.core.Utils
import deskbar.interfaces.Module
import deskbar.interfaces.Match
from deskbar.handlers.actions.CopyToClipboardAction import CopyToClipboardAction
import cgi
import gtk
import re
import math
if _debug:
import traceback
HANDLERS = ["CalculatorModule"]
def bin (n):
"""A local binary equivalent of the hex and oct builtins."""
if (n == 0):
return "0b0"
s = ""
if (n < 0):
while n != -1:
s = str (n & 1) + s
n >>= 1
return "0b" + "...111" + s
else:
while n != 0:
s = str (n & 1) + s
n >>= 1
return "0b" + s
# These next three make sure {hex, oct, bin} can handle floating point,
# by rounding. This makes sure things like hex(255/2) behave as a
# programmer would expect while allowing 255/2 to equal 127.5 for normal
# people. Abstracting out the body of these into a single function which
# takes hex, oct or bin as an argument seems to run into problems with
# those functions not being defined correctly in the resticted eval (?).
def lenient_hex (c):
try:
return hex (c)
except TypeError:
return hex (int (c))
def lenient_oct (c):
try:
return oct (c)
except TypeError:
return oct (int (c))
def lenient_bin (c):
try:
return bin (c)
except TypeError:
return bin (int (c))
class CalculatorAction (CopyToClipboardAction):
def __init__ (self, text, answer):
CopyToClipboardAction.__init__ (self, answer, answer)
self.text = text
def get_verb(self):
return _("Copy **%(origtext)s = %(name)s** to clipboard")
def get_name(self, text = None):
"""Because the text variable for history entries contains the text
typed for the history search (and not the text of the orginal action),
we store the original text seperately."""
result = CopyToClipboardAction.get_name (self, text)
result["origtext"] = self.text
return result
class CalculatorMatch (deskbar.interfaces.Match):
def __init__ (self, text, answer, **kwargs):
deskbar.interfaces.Match.__init__ (self, name = text,
icon = "gtk-add", category = "calculator", **kwargs)
self.answer = str (answer)
self.add_action (CalculatorAction (text, self.answer))
def get_hash (self):
return self.answer
class CalculatorModule (deskbar.interfaces.Module):
INFOS = {"icon": deskbar.core.Utils.load_icon ("gtk-add"),
"name": _("Calculator"),
"description": _("Calculate simple equations"),
"version" : "1.7", # Use " so the scripts can extract the version
"categories" : { "calculator" : { "name" : _("Calculator") }}}
def __init__ (self):
deskbar.interfaces.Module.__init__ (self)
self.hexre = re.compile ("0[Xx][0-9a-fA-F_]*[0-9a-fA-F]")
self.binre = re.compile ("0[bB][01_]*[01]")
def _number_parser (self, match, base):
"""A generic number parser, regardless of base. It also ignores the
'_' character so it can be used as a separator. Note how we skip
the first two characters since we assume it is something like '0x'
or '0b' and identifies the base."""
table = { '0' : 0, '1' : 1, '2' : 2, '3' : 3, '4' : 4,
'5' : 5, '6' : 6, '7' : 7, '8' : 8, '9' : 9,
'a' : 10, 'b' : 11, 'c' : 12, 'd' : 13,
'e' : 14, 'f' : 15 }
d = 0
for c in match.group()[2:]:
if c != "_":
d = d * base + table[c]
return str (d)
def _binsub (self, match):
"""Because python doesn't handle binary literals, we parse it
ourselves and replace it with a decimal representation."""
return self._number_parser (match, 2)
def _hexsub (self, match):
"""Parse the hex literal ourselves. We could let python do it, but
since we have a generic parser we use that instead."""
return self._number_parser (match, 16)
def query (self, query):
"""We evaluate the equation by first replacing hex and binary literals
with their decimal representation. (We need to check hex, so we can
distinguish 0x10b1 as a hex number, not 0x1 followed by 0b1.) We
severely restrict the eval environment. Any errors are ignored."""
restricted_dictionary = { "__builtins__" : None, "abs" : abs,
"acos" : math.acos, "asin" : math.asin,
"atan" : math.atan, "atan2" : math.atan2,
"bin" : lenient_bin,"ceil" : math.ceil,
"cos" : math.cos, "cosh" : math.cosh,
"degrees" : math.degrees,
"exp" : math.exp, "floor" : math.floor,
"hex" : lenient_hex, "int" : int,
"log" : math.log,
"log10" : math.log10, "oct" : lenient_oct,
"pi" : math.pi, "radians" : math.radians,
"round": round, "sin" : math.sin,
"sinh" : math.sinh, "sqrt" : math.sqrt,
"tan" : math.tan, "tanh" : math.tanh}
try:
scrubbedquery = query.lower()
scrubbedquery = self.hexre.sub (self._hexsub, scrubbedquery)
scrubbedquery = self.binre.sub (self._binsub, scrubbedquery)
for (c1, c2) in (("[", "("), ("{", "("), ("]", ")"), ("}", ")")):
scrubbedquery = scrubbedquery.replace (c1, c2)
answer = eval (scrubbedquery, restricted_dictionary)
# Try and avoid echoing back simple numbers. Note that this
# doesn't work well for floating point, e.g. '3.' behaves badly.
if str (answer) == query:
return []
# 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, str)):
result = [CalculatorMatch (query, answer)]
self._emit_query_ready (query, result)
# The return values aren't used by deskbar, they are used by the test
# code.
return answer
else:
return []
except:
if _debug:
traceback.print_exc ()
return []