Use a better FloatCtrl in patternEditor
This commit is contained in:
@@ -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)
|
||||
|
||||
221
util.py
221
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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user