diff --git a/gui/patternEditor.py b/gui/patternEditor.py index 7e0e3d857..f65657222 100644 --- a/gui/patternEditor.py +++ b/gui/patternEditor.py @@ -19,10 +19,9 @@ import wx import bitmapLoader -from wx.lib.intctrl import IntCtrl -from wx.lib.masked.numctrl import NumCtrl import service -from util import toClipboard, fromClipboard +from util import toClipboard, fromClipboard, FloatCtrl + ########################################################################### ## Class DmgPatternEditorDlg ########################################################################### @@ -104,26 +103,26 @@ class DmgPatternEditorDlg (wx.Dialog): self.bmpEM = wx.StaticBitmap(self, wx.ID_ANY, self.embitmap) dmgeditSizer.Add(self.bmpEM, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT, 5) - self.editEm = NumCtrl(self, wx.ID_ANY, 0, wx.DefaultPosition, defSize) - self.editEm.SetFractionWidth(2) + self.editEm = FloatCtrl(self, wx.ID_ANY, 0, wx.DefaultPosition, defSize) + self.editEm.SetPrecision(2) dmgeditSizer.Add(self.editEm, 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5) self.bmpTHERM = wx.StaticBitmap(self, wx.ID_ANY, self.thermbitmap) dmgeditSizer.Add(self.bmpTHERM, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.LEFT, 25) - self.editThermal = NumCtrl(self, wx.ID_ANY, 0, wx.DefaultPosition, defSize, 0) - self.editThermal.SetFractionWidth(2) + self.editThermal = FloatCtrl(self, wx.ID_ANY, 0, wx.DefaultPosition, defSize, 0) + self.editThermal.SetPrecision(2) dmgeditSizer.Add(self.editThermal, 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5) self.bmpKIN = wx.StaticBitmap(self, wx.ID_ANY, self.kinbitmap) dmgeditSizer.Add(self.bmpKIN, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT, 5) - self.editKinetic = NumCtrl(self, wx.ID_ANY, 0, wx.DefaultPosition, defSize) - self.editKinetic.SetFractionWidth(2) + self.editKinetic = FloatCtrl(self, wx.ID_ANY, 0, wx.DefaultPosition, defSize) + self.editKinetic.SetPrecision(2) dmgeditSizer.Add(self.editKinetic, 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5) self.bmpEXP = wx.StaticBitmap(self, wx.ID_ANY, self.expbitmap) dmgeditSizer.Add(self.bmpEXP, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.LEFT, 25) - self.editExplosive = NumCtrl(self, wx.ID_ANY, 0, wx.DefaultPosition, defSize, 0) - self.editExplosive.SetFractionWidth(2) + self.editExplosive = FloatCtrl(self, wx.ID_ANY, 0, wx.DefaultPosition, defSize, 0) + self.editExplosive.SetPrecision(2) dmgeditSizer.Add(self.editExplosive, 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5) contentSizer.Add(dmgeditSizer, 1, wx.EXPAND | wx.ALL, 5) @@ -166,12 +165,6 @@ class DmgPatternEditorDlg (wx.Dialog): bsize = self.GetBestSize() self.SetSize((-1,bsize.height)) - self.editEm.SetLimited(True) - self.editThermal.SetLimited(True) - self.editKinetic.SetLimited(True) - self.editExplosive.SetLimited(True) - - self.editEm.SetMin(0) self.editThermal.SetMin(0) self.editKinetic.SetMin(0) diff --git a/util.py b/util.py index f5a359979..a9d1eb396 100644 --- a/util.py +++ b/util.py @@ -1,5 +1,6 @@ import math import wx +import fpformat def formatAmount(val, prec=3, lowest=0, highest=0): """ @@ -91,3 +92,223 @@ def fromClipboard(): 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() +