Files
pyfa/util.py
2010-11-28 12:09:36 +02:00

315 lines
10 KiB
Python

import math
import wx
import fpformat
def formatAmount(val, prec=3, lowest=0, highest=0):
"""
Add suffix to value, transform value to match new suffix and round it.
Keyword arguments:
val -- value to process
prec -- precision of final number (number of significant positions to show)
lowest -- lowest order for suffixizing
highest -- highest order for suffixizing
"""
if val is None:
result = ""
else:
# Separate value to mantissa and suffix
mantissa, suffix = suffixizeAmount(val, lowest, highest)
# Round mantissa and add suffix
newMantissa = processAmount(mantissa, prec)
result = u"{0}{1}".format(newMantissa, suffix)
return result
def suffixizeAmount(val, lowest=-6, highest=9):
"""
Add suffix to value and transform value to match new suffix.
Keyword arguments:
val -- value to process
lowest -- lowest order for suffixizing
highest -- highest order for suffixizing
Suffixes below lowest and above highest orders won't be used.
"""
if abs(val) >= 1000 and highest >= 3:
suffixmap = {3 : "k", 6 : "M", 9 : "G"}
# Start from highest possible suffix
for key in sorted(suffixmap, reverse = True):
# Find first suitable suffix and check if it's not above highest order
if val >= 10**key and key <= highest:
return val/float(10**key), suffixmap[key]
# Take numbers between 0 and 1, and matching/below highest possible negative suffix
elif abs(val) < 1 and val != 0 and lowest <= -3:
suffixmap = {-6 : u'\u03bc', -3 : "m"}
# Start from lowest possible suffix
for key in sorted(suffixmap, reverse = False):
# Check if mantissa with next suffix is in range [1, 1000)
# Here we assume that each next order is greater than previous by 3
if val < 10**(key+3) and key >= lowest:
return val/float(10**key), suffixmap[key]
# If no suitable suffixes are found within given order borders, or value
# is already within [1, 1000) boundaries, just return rounded value with no suffix
else:
return val, ""
def processAmount(val, prec=3):
"""
Round number and return as string.
Keyword arguments:
val -- value to round
prec -- precision of final number (number of significant positions to show)
Integer numbers are not rounded, only fractional part.
"""
if val == 0: # Logarithm is not defined for zero
return "0"
roundFactor = int(prec - math.ceil(math.log10(abs(val))))
# But we don't want to round integers
if roundFactor < 0: roundFactor = 0
val = round(val, roundFactor)
# Strip trailing zero for integers and convert to string
result = str(val)[-2:] == '.0' and str(val)[:-2] or str(val)
return result
def toClipboard(text):
clip = wx.TheClipboard
clip.Open()
data = wx.TextDataObject(text)
clip.SetData(data)
clip.Close()
def fromClipboard():
clip = wx.TheClipboard
clip.Open()
data = wx.TextDataObject("")
if clip.GetData(data):
clip.Close()
return data.GetText()
else:
clip.Close()
return None
def set_float(val,default=None):
""" utility to set a floating value, useful for converting from strings """
if val in (None,''): return default
try:
return float(val)
except:
return default
class closure:
"""A very simple callback class to emulate a closure (reference to
a function with arguments) in python.
This class holds a user-defined function to be executed when the
class is invoked as a function. This is useful in many situations,
especially for 'callbacks' where lambda's are quite enough.
Many Tkinter 'actions' can use such callbacks.
>>>def my_action(x=None):
... print 'my action: x = ', x
>>>c = closure(my_action,x=1)
..... sometime later ...
>>>c()
my action: x = 1
>>>c(x=2)
my action: x = 2
based on Command class from J. Grayson's Tkinter book.
"""
def __init__(self,func=None,*args, **kw):
self.func = func
self.kw = kw
self.args = args
def __call__(self, *args, **kw):
self.kw.update(kw)
if (self.func == None): return None
self.args = args
return apply(self.func,self.args,self.kw)
class FloatCtrl(wx.TextCtrl):
""" Numerical Float Control::
a wx.TextCtrl that allows only numerical input, can take a precision argument
and optional upper / lower bounds
"""
def __init__(self, parent, value='', min='', max='',
action=None, precision=3, action_kw={}, **kwargs):
self.__digits = '0123456789.-'
self.__prec = precision
if precision is None: self.__prec = 0
self.format = '%%.%if' % self.__prec
self.__val = set_float(value)
self.__max = set_float(max)
self.__min = set_float(min)
self.fgcol_valid = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOWTEXT)
self.bgcol_valid = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)
self.fgcol_invalid ="Red"
self.bgcol_invalid =(254,254,80)
# set up action
self.__action = closure()
if callable(action): self.__action.func = action
if len(action_kw.keys())>0: self.__action.kw = action_kw
this_sty = wx.TE_PROCESS_ENTER
kw = kwargs
if kw.has_key('style'): this_sty = this_sty | kw['style']
kw['style'] = this_sty
wx.TextCtrl.__init__(self, parent, wx.ID_ANY, **kw)
self.__CheckValid(self.__val)
self.SetValue(self.__val)
self.Bind(wx.EVT_CHAR, self.onChar)
# self.Bind(wx.EVT_CHAR, self.CharEvent)
self.Bind(wx.EVT_TEXT, self.onText)
self.Bind(wx.EVT_SET_FOCUS, self.onSetFocus)
self.Bind(wx.EVT_KILL_FOCUS, self.onKillFocus)
self.Bind(wx.EVT_SIZE, self.onResize)
self.__GetMark()
def SetAction(self,action,action_kw={}):
self.__action = closure()
if callable(action): self.__action.func = action
if len(action_kw.keys())>0: self.__action.kw = action_kw
def SetPrecision(self,p):
if p is None: p = 0
self.__prec = p
self.format = '%%.%if' % p
def __GetMark(self):
" keep track of cursor position within text"
try:
self.__mark = min(wx.TextCtrl.GetSelection(self)[0],
len(wx.TextCtrl.GetValue(self).strip()))
except:
self.__mark = 0
def __SetMark(self,m=None):
" "
if m==None: m = self.__mark
self.SetSelection(m,m)
def SetValue(self,value=None,act=True):
" main method to set value "
# print 'Set Value '
if value == None: value = wx.TextCtrl.GetValue(self).strip()
self.__CheckValid(value)
self.__GetMark()
if self.__valid:
self.__Text_SetValue(self.__val)
self.SetForegroundColour(self.fgcol_valid)
self.SetBackgroundColour(self.bgcol_valid)
if callable(self.__action) and act: self.__action(value=self.__val)
else:
self.__val = self.__bound_val
self.__Text_SetValue(self.__val)
self.__CheckValid(self.__val)
self.SetForegroundColour(self.fgcol_invalid)
self.SetBackgroundColour(self.bgcol_invalid)
self.__SetMark()
def onKillFocus(self, event):
self.__GetMark()
event.Skip()
def onResize(self, event):
event.Skip()
def onSetFocus(self, event=None):
self.__SetMark()
if event: event.Skip()
def onChar(self, event):
""" on Character event"""
key = event.GetKeyCode()
entry = wx.TextCtrl.GetValue(self).strip()
pos = wx.TextCtrl.GetSelection(self)
# really, the order here is important:
# 1. return sends to ValidateEntry
if (key == wx.WXK_RETURN):
self.SetValue(entry)
return
# 2. other non-text characters are passed without change
if (key < wx.WXK_SPACE or key == wx.WXK_DELETE or key > 255):
event.Skip()
return
# 3. check for multiple '.' and out of place '-' signs and ignore these
# note that chr(key) will now work due to return at #2
has_minus = '-' in entry
ckey = chr(key)
if ((ckey == '.' and (self.__prec == 0 or '.' in entry) ) or
(ckey == '-' and (has_minus or pos[0] != 0)) or
(ckey != '-' and has_minus and pos[0] == 0)):
return
# 4. allow digits, but not other characters
if (chr(key) in self.__digits):
event.Skip()
return
# return without event.Skip() : do not propagate event
return
def onText(self, event=None):
try:
if event.GetString() != '':
self.__CheckValid(event.GetString())
except:
pass
event.Skip()
def GetValue(self):
if self.__prec > 0:
return set_float(fpformat.fix(self.__val, self.__prec))
else:
return int(self.__val)
def GetMin(self): return self.__min
def GetMax(self): return self.__max
def SetMin(self,min): self.__min = set_float(min)
def SetMax(self,max): self.__max = set_float(max)
def __Text_SetValue(self,value):
wx.TextCtrl.SetValue(self, self.format % set_float(value))
self.Refresh()
def __CheckValid(self,value):
# print ' Check valid ', value
v = self.__val
try:
self.__valid = True
v = set_float(value)
if self.__min != None and (v < self.__min):
self.__valid = False
v = self.__min
if self.__max != None and (v > self.__max):
self.__valid = False
v = self.__max
except:
self.__valid = False
self.__bound_val = v
if self.__valid:
self.__bound_val = self.__val = v
self.SetForegroundColour(self.fgcol_valid)
self.SetBackgroundColour(self.bgcol_valid)
else:
self.SetForegroundColour(self.fgcol_invalid)
self.SetBackgroundColour(self.bgcol_invalid)
self.Refresh()