Patches from Will Sadkin:
MaskedEditMixin: - fixed size calculations on changing fonts - fixed tabbing logic now that tab events are entered into the control by default (ie event.Skip()) if wx.TE_PROCESS_TAB is set - fixed code attempting to fix the selection after focus events generated on control destruction, to prevent tracebacks TextCtrl, ComboBox - Added support for XRC - Fixed sizing calculation code - Added SetFont() override method that will recalculate the size if this is called. - Added AppendItems() for ComboBox NumCtrl: - prevented ctrl from accepting same grouping and decimal character, - fixed issue preventing paste from working if decimal char was different than '.' TimeCtrl: - Fixed default value to use 24hour time (which will be converted appropriately if format supports it, and added code to check if local timezone uses "AM/PM" for this concept; if not, control now defaults to 24hour format, and disallows the am/pm form. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@28400 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
@@ -12,8 +12,8 @@
|
|||||||
|
|
||||||
# import relevant external symbols into package namespace:
|
# import relevant external symbols into package namespace:
|
||||||
from maskededit import *
|
from maskededit import *
|
||||||
from textctrl import BaseMaskedTextCtrl, TextCtrl
|
from textctrl import BaseMaskedTextCtrl, PreMaskedTextCtrl, TextCtrl
|
||||||
from combobox import BaseMaskedComboBox, ComboBox, MaskedComboBoxSelectEvent
|
from combobox import BaseMaskedComboBox, PreMaskedComboBox, ComboBox, MaskedComboBoxSelectEvent
|
||||||
from numctrl import NumCtrl, wxEVT_COMMAND_MASKED_NUMBER_UPDATED, EVT_NUM, NumberUpdatedEvent
|
from numctrl import NumCtrl, wxEVT_COMMAND_MASKED_NUMBER_UPDATED, EVT_NUM, NumberUpdatedEvent
|
||||||
from timectrl import TimeCtrl, wxEVT_TIMEVAL_UPDATED, EVT_TIMEUPDATE, TimeUpdatedEvent
|
from timectrl import TimeCtrl, wxEVT_TIMEVAL_UPDATED, EVT_TIMEUPDATE, TimeUpdatedEvent
|
||||||
from ipaddrctrl import IpAddrCtrl
|
from ipaddrctrl import IpAddrCtrl
|
||||||
|
@@ -54,11 +54,6 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
|
|||||||
**kwargs):
|
**kwargs):
|
||||||
|
|
||||||
|
|
||||||
# This is necessary, because wxComboBox currently provides no
|
|
||||||
# method for determining later if this was specified in the
|
|
||||||
# constructor for the control...
|
|
||||||
self.__readonly = style & wx.CB_READONLY == wx.CB_READONLY
|
|
||||||
|
|
||||||
kwargs['choices'] = choices ## set up maskededit to work with choice list too
|
kwargs['choices'] = choices ## set up maskededit to work with choice list too
|
||||||
|
|
||||||
## Since combobox completion is case-insensitive, always validate same way
|
## Since combobox completion is case-insensitive, always validate same way
|
||||||
@@ -80,15 +75,56 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
|
|||||||
choices=choices, style=style|wx.WANTS_CHARS,
|
choices=choices, style=style|wx.WANTS_CHARS,
|
||||||
validator=validator,
|
validator=validator,
|
||||||
name=name)
|
name=name)
|
||||||
|
|
||||||
self.controlInitialized = True
|
self.controlInitialized = True
|
||||||
|
|
||||||
|
self._PostInit(style=style, setupEventHandling=setupEventHandling,
|
||||||
|
name=name, value=value, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def _PostInit(self, style=wx.CB_DROPDOWN,
|
||||||
|
setupEventHandling = True, ## setup event handling by default):
|
||||||
|
name = "maskedComboBox", value='', **kwargs):
|
||||||
|
|
||||||
|
# This is necessary, because wxComboBox currently provides no
|
||||||
|
# method for determining later if this was specified in the
|
||||||
|
# constructor for the control...
|
||||||
|
self.__readonly = style & wx.CB_READONLY == wx.CB_READONLY
|
||||||
|
|
||||||
|
if not hasattr(self, 'controlInitialized'):
|
||||||
|
|
||||||
|
self.controlInitialized = True ## must have been called via XRC, therefore base class is constructed
|
||||||
|
if not kwargs.has_key('choices'):
|
||||||
|
choices=[]
|
||||||
|
kwargs['choices'] = choices ## set up maskededit to work with choice list too
|
||||||
|
self._choices = []
|
||||||
|
|
||||||
|
## Since combobox completion is case-insensitive, always validate same way
|
||||||
|
if not kwargs.has_key('compareNoCase'):
|
||||||
|
kwargs['compareNoCase'] = True
|
||||||
|
|
||||||
|
MaskedEditMixin.__init__( self, name, **kwargs )
|
||||||
|
|
||||||
|
self._choices = self._ctrl_constraints._choices
|
||||||
|
## dbg('self._choices:', self._choices)
|
||||||
|
|
||||||
|
if self._ctrl_constraints._alignRight:
|
||||||
|
choices = [choice.rjust(self._masklength) for choice in choices]
|
||||||
|
else:
|
||||||
|
choices = [choice.ljust(self._masklength) for choice in choices]
|
||||||
|
wx.ComboBox.Clear(self)
|
||||||
|
wx.ComboBox.AppendItems(self, choices)
|
||||||
|
|
||||||
|
|
||||||
# Set control font - fixed width by default
|
# Set control font - fixed width by default
|
||||||
self._setFont()
|
self._setFont()
|
||||||
|
|
||||||
if self._autofit:
|
if self._autofit:
|
||||||
self.SetClientSize(self._CalcSize())
|
self.SetClientSize(self._CalcSize())
|
||||||
self.SetSizeHints(self.GetSize())
|
width = self.GetSize().width
|
||||||
|
height = self.GetBestSize().height
|
||||||
|
self.SetSize((width, height))
|
||||||
|
self.SetSizeHints((width, height))
|
||||||
|
|
||||||
|
|
||||||
if value:
|
if value:
|
||||||
# ensure value is width of the mask of the control:
|
# ensure value is width of the mask of the control:
|
||||||
@@ -134,6 +170,19 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
|
|||||||
return (size[0]+20, size[1])
|
return (size[0]+20, size[1])
|
||||||
|
|
||||||
|
|
||||||
|
def SetFont(self, *args, **kwargs):
|
||||||
|
""" Set the font, then recalculate control size, if appropriate. """
|
||||||
|
wx.ComboBox.SetFont(self, *args, **kwargs)
|
||||||
|
if self._autofit:
|
||||||
|
dbg('calculated size:', self._CalcSize())
|
||||||
|
self.SetClientSize(self._CalcSize())
|
||||||
|
width = self.GetSize().width
|
||||||
|
height = self.GetBestSize().height
|
||||||
|
dbg('setting client size to:', (width, height))
|
||||||
|
self.SetSize((width, height))
|
||||||
|
self.SetSizeHints((width, height))
|
||||||
|
|
||||||
|
|
||||||
def _GetSelection(self):
|
def _GetSelection(self):
|
||||||
"""
|
"""
|
||||||
Allow mixin to get the text selection of this control.
|
Allow mixin to get the text selection of this control.
|
||||||
@@ -306,7 +355,6 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
|
|||||||
else:
|
else:
|
||||||
wx.ComboBox.Undo() # else revert to base control behavior
|
wx.ComboBox.Undo() # else revert to base control behavior
|
||||||
|
|
||||||
|
|
||||||
def Append( self, choice, clientData=None ):
|
def Append( self, choice, clientData=None ):
|
||||||
"""
|
"""
|
||||||
This function override is necessary so we can keep track of any additions to the list
|
This function override is necessary so we can keep track of any additions to the list
|
||||||
@@ -352,6 +400,13 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
|
|||||||
wx.ComboBox.Append(self, choice, clientData)
|
wx.ComboBox.Append(self, choice, clientData)
|
||||||
|
|
||||||
|
|
||||||
|
def AppendItems( self, choices ):
|
||||||
|
"""
|
||||||
|
AppendItems() is handled in terms of Append, to avoid code replication.
|
||||||
|
"""
|
||||||
|
for choice in choices:
|
||||||
|
self.Append(choice)
|
||||||
|
|
||||||
|
|
||||||
def Clear( self ):
|
def Clear( self ):
|
||||||
"""
|
"""
|
||||||
@@ -544,3 +599,30 @@ class ComboBox( BaseMaskedComboBox, MaskedEditAccessorsMixin ):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PreMaskedComboBox( BaseMaskedComboBox, MaskedEditAccessorsMixin ):
|
||||||
|
"""
|
||||||
|
This allows us to use XRC subclassing.
|
||||||
|
"""
|
||||||
|
# This should really be wx.EVT_WINDOW_CREATE but it is not
|
||||||
|
# currently delivered for native controls on all platforms, so
|
||||||
|
# we'll use EVT_SIZE instead. It should happen shortly after the
|
||||||
|
# control is created as the control is set to its "best" size.
|
||||||
|
_firstEventType = wx.EVT_SIZE
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pre = wx.PreComboBox()
|
||||||
|
self.PostCreate(pre)
|
||||||
|
self.Bind(self._firstEventType, self.OnCreate)
|
||||||
|
|
||||||
|
|
||||||
|
def OnCreate(self, evt):
|
||||||
|
self.Unbind(self._firstEventType)
|
||||||
|
self._PostInit()
|
||||||
|
|
||||||
|
i=0
|
||||||
|
## CHANGELOG:
|
||||||
|
## ====================
|
||||||
|
## Version 1.1
|
||||||
|
## 1. Added .SetFont() method that properly resizes control
|
||||||
|
## 2. Modified control to support construction via XRC mechanism.
|
||||||
|
## 3. Added AppendItems() to conform with latest combobox.
|
||||||
|
@@ -757,7 +757,7 @@ import wx
|
|||||||
from wx.tools.dbg import Logger
|
from wx.tools.dbg import Logger
|
||||||
|
|
||||||
dbg = Logger()
|
dbg = Logger()
|
||||||
##dbg(enable=0)
|
##dbg(enable=1)
|
||||||
|
|
||||||
## ---------- ---------- ---------- ---------- ---------- ---------- ----------
|
## ---------- ---------- ---------- ---------- ---------- ---------- ----------
|
||||||
|
|
||||||
@@ -1914,9 +1914,13 @@ class MaskedEditMixin:
|
|||||||
self._prevValue = newvalue # disallow undo of sign type
|
self._prevValue = newvalue # disallow undo of sign type
|
||||||
|
|
||||||
if self._autofit:
|
if self._autofit:
|
||||||
## dbg('setting client size to:', self._CalcSize())
|
## dbg('calculated size:', self._CalcSize())
|
||||||
self.SetClientSize(self._CalcSize())
|
self.SetClientSize(self._CalcSize())
|
||||||
self.SetSizeHints(self.GetSize())
|
width = self.GetSize().width
|
||||||
|
height = self.GetBestSize().height
|
||||||
|
## dbg('setting client size to:', (width, height))
|
||||||
|
self.SetSize((width, height))
|
||||||
|
self.SetSizeHints((width, height))
|
||||||
|
|
||||||
# Set value/type-specific formatting
|
# Set value/type-specific formatting
|
||||||
self._applyFormatting()
|
self._applyFormatting()
|
||||||
@@ -1991,8 +1995,24 @@ class MaskedEditMixin:
|
|||||||
self._SetInitialValue()
|
self._SetInitialValue()
|
||||||
|
|
||||||
if self._autofit:
|
if self._autofit:
|
||||||
|
# this is tricky, because, as Robin explains:
|
||||||
|
# "Basically there are two sizes to deal with, that are potentially
|
||||||
|
# different. The client size is the inside size and may, depending
|
||||||
|
# on platform, exclude the borders and such. The normal size is
|
||||||
|
# the outside size that does include the borders. What you are
|
||||||
|
# calculating (in _CalcSize) is the client size, but the sizers
|
||||||
|
# deal with the full size and so that is the minimum size that
|
||||||
|
# we need to set with SetSizeHints. The root of the problem is
|
||||||
|
# that in _calcSize the current client size height is returned,
|
||||||
|
# instead of a height based on the current font. So I suggest using
|
||||||
|
# _calcSize to just get the width, and then use GetBestSize to
|
||||||
|
# get the height."
|
||||||
self.SetClientSize(self._CalcSize())
|
self.SetClientSize(self._CalcSize())
|
||||||
self.SetSizeHints(self.GetSize())
|
width = self.GetSize().width
|
||||||
|
height = self.GetBestSize().height
|
||||||
|
self.SetSize((width, height))
|
||||||
|
self.SetSizeHints((width, height))
|
||||||
|
|
||||||
|
|
||||||
# Set value/type-specific formatting
|
# Set value/type-specific formatting
|
||||||
self._applyFormatting()
|
self._applyFormatting()
|
||||||
@@ -2642,7 +2662,7 @@ class MaskedEditMixin:
|
|||||||
sizing_text += 'M'
|
sizing_text += 'M'
|
||||||
#### dbg('len(sizing_text):', len(sizing_text), 'sizing_text: "%s"' % sizing_text)
|
#### dbg('len(sizing_text):', len(sizing_text), 'sizing_text: "%s"' % sizing_text)
|
||||||
w, h = self.GetTextExtent(sizing_text)
|
w, h = self.GetTextExtent(sizing_text)
|
||||||
size = (w+4, self.GetClientSize().height)
|
size = (w+4, self.GetSize().height)
|
||||||
#### dbg('size:', size, indent=0)
|
#### dbg('size:', size, indent=0)
|
||||||
return size
|
return size
|
||||||
|
|
||||||
@@ -2690,7 +2710,7 @@ class MaskedEditMixin:
|
|||||||
## dbg('ignoring bogus text change event', indent=0)
|
## dbg('ignoring bogus text change event', indent=0)
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
## dbg('curvalue: "%s", newvalue: "%s"' % (self._curValue, newvalue))
|
## dbg('curvalue: "%s", newvalue: "%s", len(newvalue): %d' % (self._curValue, newvalue, len(newvalue)))
|
||||||
if self._Change():
|
if self._Change():
|
||||||
if self._signOk and self._isNeg and newvalue.find('-') == -1 and newvalue.find('(') == -1:
|
if self._signOk and self._isNeg and newvalue.find('-') == -1 and newvalue.find('(') == -1:
|
||||||
## dbg('clearing self._isNeg')
|
## dbg('clearing self._isNeg')
|
||||||
@@ -2864,6 +2884,8 @@ class MaskedEditMixin:
|
|||||||
if newfield != field and newfield._selectOnFieldEntry:
|
if newfield != field and newfield._selectOnFieldEntry:
|
||||||
## dbg('queuing selection: (%d, %d)' % (newfield._extent[0], newfield._extent[1]))
|
## dbg('queuing selection: (%d, %d)' % (newfield._extent[0], newfield._extent[1]))
|
||||||
wx.CallAfter(self._SetSelection, newfield._extent[0], newfield._extent[1])
|
wx.CallAfter(self._SetSelection, newfield._extent[0], newfield._extent[1])
|
||||||
|
else:
|
||||||
|
wx.CallAfter(self._SetSelection, newpos, new_select_to)
|
||||||
keep_processing = False
|
keep_processing = False
|
||||||
|
|
||||||
elif keep_processing:
|
elif keep_processing:
|
||||||
@@ -3400,12 +3422,12 @@ class MaskedEditMixin:
|
|||||||
|
|
||||||
def _OnReturn(self, event):
|
def _OnReturn(self, event):
|
||||||
"""
|
"""
|
||||||
Changes the event to look like a tab event, so we can then call
|
Swallows the return, issues a Navigate event instead, since
|
||||||
event.Skip() on it, and have the parent form "do the right thing."
|
masked controls are "single line" by defn.
|
||||||
"""
|
"""
|
||||||
## dbg('MaskedEditMixin::OnReturn')
|
## dbg('MaskedEditMixin::OnReturn')
|
||||||
event.m_keyCode = wx.WXK_TAB
|
self.Navigate(True)
|
||||||
event.Skip()
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _OnHome(self,event):
|
def _OnHome(self,event):
|
||||||
@@ -3486,9 +3508,6 @@ class MaskedEditMixin:
|
|||||||
"""
|
"""
|
||||||
Primarily handles TAB events, but can be used for any key that
|
Primarily handles TAB events, but can be used for any key that
|
||||||
designer wants to change fields within a masked edit control.
|
designer wants to change fields within a masked edit control.
|
||||||
NOTE: at the moment, although coded to handle shift-TAB and
|
|
||||||
control-shift-TAB, these events are not sent to the controls
|
|
||||||
by the framework.
|
|
||||||
"""
|
"""
|
||||||
## dbg('MaskedEditMixin::_OnChangeField', indent = 1)
|
## dbg('MaskedEditMixin::_OnChangeField', indent = 1)
|
||||||
# determine end of current field:
|
# determine end of current field:
|
||||||
@@ -3500,7 +3519,10 @@ class MaskedEditMixin:
|
|||||||
self._AdjustField(pos)
|
self._AdjustField(pos)
|
||||||
if event.GetKeyCode() == wx.WXK_TAB:
|
if event.GetKeyCode() == wx.WXK_TAB:
|
||||||
## dbg('tab to next ctrl')
|
## dbg('tab to next ctrl')
|
||||||
event.Skip()
|
# As of 2.5.2, you don't call event.Skip() to do
|
||||||
|
# this, but instead force explicit navigation, if
|
||||||
|
# wx.TE_PROCESS_TAB is used (like in the masked edits)
|
||||||
|
self.Navigate(True)
|
||||||
#else: do nothing
|
#else: do nothing
|
||||||
## dbg(indent=0)
|
## dbg(indent=0)
|
||||||
return False
|
return False
|
||||||
@@ -3534,7 +3556,10 @@ class MaskedEditMixin:
|
|||||||
self._AdjustField(pos)
|
self._AdjustField(pos)
|
||||||
if event.GetKeyCode() == wx.WXK_TAB:
|
if event.GetKeyCode() == wx.WXK_TAB:
|
||||||
## dbg('tab to previous ctrl')
|
## dbg('tab to previous ctrl')
|
||||||
event.Skip()
|
# As of 2.5.2, you don't call event.Skip() to do
|
||||||
|
# this, but instead force explicit navigation, if
|
||||||
|
# wx.TE_PROCESS_TAB is used (like in the masked edits)
|
||||||
|
self.Navigate(False)
|
||||||
else:
|
else:
|
||||||
## dbg('position at beginning')
|
## dbg('position at beginning')
|
||||||
wx.CallAfter(self._SetInsertionPoint, field_start)
|
wx.CallAfter(self._SetInsertionPoint, field_start)
|
||||||
@@ -3580,7 +3605,10 @@ class MaskedEditMixin:
|
|||||||
self._AdjustField(pos)
|
self._AdjustField(pos)
|
||||||
if event.GetKeyCode() == wx.WXK_TAB:
|
if event.GetKeyCode() == wx.WXK_TAB:
|
||||||
## dbg('tab to next ctrl')
|
## dbg('tab to next ctrl')
|
||||||
event.Skip()
|
# As of 2.5.2, you don't call event.Skip() to do
|
||||||
|
# this, but instead force explicit navigation, if
|
||||||
|
# wx.TE_PROCESS_TAB is used (like in the masked edits)
|
||||||
|
self.Navigate(True)
|
||||||
else:
|
else:
|
||||||
## dbg('position at end')
|
## dbg('position at end')
|
||||||
wx.CallAfter(self._SetInsertionPoint, field_end)
|
wx.CallAfter(self._SetInsertionPoint, field_end)
|
||||||
@@ -3594,7 +3622,10 @@ class MaskedEditMixin:
|
|||||||
self._AdjustField(pos)
|
self._AdjustField(pos)
|
||||||
if event.GetKeyCode() == wx.WXK_TAB:
|
if event.GetKeyCode() == wx.WXK_TAB:
|
||||||
## dbg('tab to next ctrl')
|
## dbg('tab to next ctrl')
|
||||||
event.Skip()
|
# As of 2.5.2, you don't call event.Skip() to do
|
||||||
|
# this, but instead force explicit navigation, if
|
||||||
|
# wx.TE_PROCESS_TAB is used (like in the masked edits)
|
||||||
|
self.Navigate(True)
|
||||||
#else: do nothing
|
#else: do nothing
|
||||||
## dbg(indent=0)
|
## dbg(indent=0)
|
||||||
return False
|
return False
|
||||||
@@ -3640,6 +3671,8 @@ class MaskedEditMixin:
|
|||||||
if fraction._selectOnFieldEntry:
|
if fraction._selectOnFieldEntry:
|
||||||
## dbg('queuing selection after decimal point to:', (start, end))
|
## dbg('queuing selection after decimal point to:', (start, end))
|
||||||
wx.CallAfter(self._SetSelection, start, end)
|
wx.CallAfter(self._SetSelection, start, end)
|
||||||
|
else:
|
||||||
|
wx.CallAfter(self._SetSelection, start, start)
|
||||||
keep_processing = False
|
keep_processing = False
|
||||||
|
|
||||||
if self._isInt: ## handle integer value, truncate from current position
|
if self._isInt: ## handle integer value, truncate from current position
|
||||||
@@ -3654,6 +3687,7 @@ class MaskedEditMixin:
|
|||||||
if newstr.find(')') != -1:
|
if newstr.find(')') != -1:
|
||||||
newpos -= 1 # (don't move past right paren)
|
newpos -= 1 # (don't move past right paren)
|
||||||
wx.CallAfter(self._SetInsertionPoint, newpos)
|
wx.CallAfter(self._SetInsertionPoint, newpos)
|
||||||
|
wx.CallAfter(self._SetSelection, newpos, newpos)
|
||||||
keep_processing = False
|
keep_processing = False
|
||||||
## dbg(indent=0)
|
## dbg(indent=0)
|
||||||
|
|
||||||
@@ -3949,6 +3983,7 @@ class MaskedEditMixin:
|
|||||||
pos = pos+2
|
pos = pos+2
|
||||||
|
|
||||||
if newvalue != value:
|
if newvalue != value:
|
||||||
|
## dbg('old value: "%s"\nnew value: "%s"' % (value, newvalue))
|
||||||
self._SetValue(newvalue)
|
self._SetValue(newvalue)
|
||||||
self._SetInsertionPoint(pos)
|
self._SetInsertionPoint(pos)
|
||||||
|
|
||||||
@@ -4042,6 +4077,8 @@ class MaskedEditMixin:
|
|||||||
self._SetInsertionPoint(pos)
|
self._SetInsertionPoint(pos)
|
||||||
if pos < sel_to: # restore selection
|
if pos < sel_to: # restore selection
|
||||||
self._SetSelection(pos, sel_to)
|
self._SetSelection(pos, sel_to)
|
||||||
|
else:
|
||||||
|
self._SetSelection(pos, pos)
|
||||||
## dbg('adjusted pos:', pos, indent=0)
|
## dbg('adjusted pos:', pos, indent=0)
|
||||||
return pos
|
return pos
|
||||||
|
|
||||||
@@ -5182,7 +5219,10 @@ class MaskedEditMixin:
|
|||||||
the control, and deselect.
|
the control, and deselect.
|
||||||
"""
|
"""
|
||||||
## dbg('MaskedEditMixin::_fixSelection', indent=1)
|
## dbg('MaskedEditMixin::_fixSelection', indent=1)
|
||||||
if not self._mask or not self._IsEditable():
|
# can get here if called with wx.CallAfter after underlying
|
||||||
|
# control has been destroyed on close, but after focus
|
||||||
|
# events
|
||||||
|
if not self or not self._mask or not self._IsEditable():
|
||||||
## dbg(indent=0)
|
## dbg(indent=0)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -6396,6 +6436,8 @@ i=1
|
|||||||
## chars properly.)
|
## chars properly.)
|
||||||
## 4. Fixed autoselect behavior to work similarly to (2) above, so that combobox
|
## 4. Fixed autoselect behavior to work similarly to (2) above, so that combobox
|
||||||
## selection will only select the non-empty text, as per request.
|
## selection will only select the non-empty text, as per request.
|
||||||
|
## 5. Fixed tabbing to work with 2.5.2 semantics.
|
||||||
|
## 6. Fixed size calculation to handle changing fonts
|
||||||
##
|
##
|
||||||
## Version 1.6
|
## Version 1.6
|
||||||
## 1. Reorganized masked controls into separate package, renamed things accordingly
|
## 1. Reorganized masked controls into separate package, renamed things accordingly
|
||||||
|
@@ -386,7 +386,7 @@ MININT = -maxint-1
|
|||||||
from wx.tools.dbg import Logger
|
from wx.tools.dbg import Logger
|
||||||
from wx.lib.masked import MaskedEditMixin, Field, BaseMaskedTextCtrl
|
from wx.lib.masked import MaskedEditMixin, Field, BaseMaskedTextCtrl
|
||||||
dbg = Logger()
|
dbg = Logger()
|
||||||
##dbg(enable=0)
|
##dbg(enable=1)
|
||||||
|
|
||||||
#----------------------------------------------------------------------------
|
#----------------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -654,14 +654,26 @@ class NumCtrl(BaseMaskedTextCtrl, NumCtrlAccessorsMixin):
|
|||||||
|
|
||||||
maskededit_kwargs['mask'] = intmask+fracmask
|
maskededit_kwargs['mask'] = intmask+fracmask
|
||||||
|
|
||||||
if kwargs.has_key('groupChar'):
|
if kwargs.has_key('groupChar') or kwargs.has_key('decimalChar'):
|
||||||
old_groupchar = self._groupChar # save so we can reformat properly
|
old_groupchar = self._groupChar # save so we can reformat properly
|
||||||
## dbg("old_groupchar: '%s'" % old_groupchar)
|
|
||||||
maskededit_kwargs['groupChar'] = kwargs['groupChar']
|
|
||||||
if kwargs.has_key('decimalChar'):
|
|
||||||
old_decimalchar = self._decimalChar
|
old_decimalchar = self._decimalChar
|
||||||
|
## dbg("old_groupchar: '%s'" % old_groupchar)
|
||||||
## dbg("old_decimalchar: '%s'" % old_decimalchar)
|
## dbg("old_decimalchar: '%s'" % old_decimalchar)
|
||||||
|
groupchar = old_groupchar
|
||||||
|
decimalchar = old_decimalchar
|
||||||
|
|
||||||
|
if kwargs.has_key('groupChar'):
|
||||||
|
maskededit_kwargs['groupChar'] = kwargs['groupChar']
|
||||||
|
groupchar = kwargs['groupChar']
|
||||||
|
if kwargs.has_key('decimalChar'):
|
||||||
maskededit_kwargs['decimalChar'] = kwargs['decimalChar']
|
maskededit_kwargs['decimalChar'] = kwargs['decimalChar']
|
||||||
|
decimalchar = kwargs['decimalChar']
|
||||||
|
|
||||||
|
# Add sanity check to make sure these are distinct, and if not,
|
||||||
|
# raise attribute error
|
||||||
|
if groupchar == decimalchar:
|
||||||
|
raise AttributeError('groupChar and decimalChar must be distinct')
|
||||||
|
|
||||||
|
|
||||||
# for all other parameters, assign keyword args as appropriate:
|
# for all other parameters, assign keyword args as appropriate:
|
||||||
for key, param_value in kwargs.items():
|
for key, param_value in kwargs.items():
|
||||||
@@ -1089,9 +1101,8 @@ class NumCtrl(BaseMaskedTextCtrl, NumCtrlAccessorsMixin):
|
|||||||
and value[sel_start:sel_to] == self._groupChar ):
|
and value[sel_start:sel_to] == self._groupChar ):
|
||||||
self.SetInsertionPoint(sel_start)
|
self.SetInsertionPoint(sel_start)
|
||||||
self.SetSelection(sel_start, sel_to+1)
|
self.SetSelection(sel_start, sel_to+1)
|
||||||
|
|
||||||
return BaseMaskedTextCtrl._OnErase(self, event, just_return_value)
|
|
||||||
## dbg(indent=0)
|
## dbg(indent=0)
|
||||||
|
return BaseMaskedTextCtrl._OnErase(self, event, just_return_value)
|
||||||
|
|
||||||
|
|
||||||
def OnTextChange( self, event ):
|
def OnTextChange( self, event ):
|
||||||
@@ -1146,7 +1157,9 @@ class NumCtrl(BaseMaskedTextCtrl, NumCtrlAccessorsMixin):
|
|||||||
A ValueError exception will be raised if an invalid value
|
A ValueError exception will be raised if an invalid value
|
||||||
is specified.
|
is specified.
|
||||||
"""
|
"""
|
||||||
|
## dbg('NumCtrl::SetValue(%s)' % value, indent=1)
|
||||||
BaseMaskedTextCtrl.SetValue( self, self._toGUI(value) )
|
BaseMaskedTextCtrl.SetValue( self, self._toGUI(value) )
|
||||||
|
## dbg(indent=0)
|
||||||
|
|
||||||
|
|
||||||
def SetIntegerWidth(self, value):
|
def SetIntegerWidth(self, value):
|
||||||
@@ -1519,7 +1532,7 @@ class NumCtrl(BaseMaskedTextCtrl, NumCtrlAccessorsMixin):
|
|||||||
Preprocessor for base control paste; if value needs to be right-justified
|
Preprocessor for base control paste; if value needs to be right-justified
|
||||||
to fit in control, do so prior to paste:
|
to fit in control, do so prior to paste:
|
||||||
"""
|
"""
|
||||||
## dbg('NumCtrl::_Paste (value = "%s")' % value)
|
## dbg('NumCtrl::_Paste (value = "%s")' % value, indent=1)
|
||||||
if value is None:
|
if value is None:
|
||||||
paste_text = self._getClipboardContents()
|
paste_text = self._getClipboardContents()
|
||||||
else:
|
else:
|
||||||
@@ -1533,7 +1546,7 @@ class NumCtrl(BaseMaskedTextCtrl, NumCtrlAccessorsMixin):
|
|||||||
#
|
#
|
||||||
field = self._FindField(sel_start)
|
field = self._FindField(sel_start)
|
||||||
edit_start, edit_end = field._extent
|
edit_start, edit_end = field._extent
|
||||||
paste_text = paste_text.replace(self._groupChar, '').replace(self._decimalChar, '.').replace('(', '-').replace(')','')
|
paste_text = paste_text.replace(self._groupChar, '').replace('(', '-').replace(')','')
|
||||||
if field._insertRight and self._groupDigits:
|
if field._insertRight and self._groupDigits:
|
||||||
# want to paste to the left; see if it will fit:
|
# want to paste to the left; see if it will fit:
|
||||||
left_text = old_value[edit_start:sel_start].lstrip()
|
left_text = old_value[edit_start:sel_start].lstrip()
|
||||||
@@ -1564,12 +1577,6 @@ class NumCtrl(BaseMaskedTextCtrl, NumCtrlAccessorsMixin):
|
|||||||
self.SetInsertionPoint(sel_to)
|
self.SetInsertionPoint(sel_to)
|
||||||
self.SetSelection(sel_start, sel_to)
|
self.SetSelection(sel_start, sel_to)
|
||||||
|
|
||||||
## # treat paste as "replace number", if appropriate:
|
|
||||||
## sel_start, sel_to = self._GetSelection()
|
|
||||||
## if sel_start == sel_to or self._selectOnEntry and (sel_start, sel_to) == self._fields[0]._extent:
|
|
||||||
## paste_text = self._toGUI(paste_text)
|
|
||||||
## self._SetSelection(0, len(self._mask))
|
|
||||||
|
|
||||||
new_text, replace_to = MaskedEditMixin._Paste(self,
|
new_text, replace_to = MaskedEditMixin._Paste(self,
|
||||||
paste_text,
|
paste_text,
|
||||||
raise_on_invalid=raise_on_invalid,
|
raise_on_invalid=raise_on_invalid,
|
||||||
|
@@ -22,7 +22,7 @@ from wx.lib.masked import *
|
|||||||
# be a good place to implement the 2.3 logger class
|
# be a good place to implement the 2.3 logger class
|
||||||
from wx.tools.dbg import Logger
|
from wx.tools.dbg import Logger
|
||||||
dbg = Logger()
|
dbg = Logger()
|
||||||
##dbg(enable=0)
|
##dbg(enable=1)
|
||||||
|
|
||||||
# ## TRICKY BIT: to avoid a ton of boiler-plate, and to
|
# ## TRICKY BIT: to avoid a ton of boiler-plate, and to
|
||||||
# ## automate the getter/setter generation for each valid
|
# ## automate the getter/setter generation for each valid
|
||||||
@@ -76,6 +76,13 @@ class BaseMaskedTextCtrl( wx.TextCtrl, MaskedEditMixin ):
|
|||||||
style=style, validator=validator,
|
style=style, validator=validator,
|
||||||
name=name)
|
name=name)
|
||||||
|
|
||||||
|
self._PostInit(setupEventHandling = setupEventHandling,
|
||||||
|
name=name, value=value,**kwargs )
|
||||||
|
|
||||||
|
|
||||||
|
def _PostInit(self,setupEventHandling=True,
|
||||||
|
name='maskedTextCtrl' , value='', **kwargs):
|
||||||
|
|
||||||
self.controlInitialized = True
|
self.controlInitialized = True
|
||||||
MaskedEditMixin.__init__( self, name, **kwargs )
|
MaskedEditMixin.__init__( self, name, **kwargs )
|
||||||
|
|
||||||
@@ -219,6 +226,18 @@ class BaseMaskedTextCtrl( wx.TextCtrl, MaskedEditMixin ):
|
|||||||
wx.CallAfter(self._SetSelection, replace_to, replace_to)
|
wx.CallAfter(self._SetSelection, replace_to, replace_to)
|
||||||
## dbg(indent=0)
|
## dbg(indent=0)
|
||||||
|
|
||||||
|
def SetFont(self, *args, **kwargs):
|
||||||
|
""" Set the font, then recalculate control size, if appropriate. """
|
||||||
|
wx.TextCtrl.SetFont(self, *args, **kwargs)
|
||||||
|
if self._autofit:
|
||||||
|
## dbg('calculated size:', self._CalcSize())
|
||||||
|
self.SetClientSize(self._CalcSize())
|
||||||
|
width = self.GetSize().width
|
||||||
|
height = self.GetBestSize().height
|
||||||
|
## dbg('setting client size to:', (width, height))
|
||||||
|
self.SetSize((width, height))
|
||||||
|
self.SetSizeHints((width, height))
|
||||||
|
|
||||||
|
|
||||||
def Clear(self):
|
def Clear(self):
|
||||||
""" Blanks the current control value by replacing it with the default value."""
|
""" Blanks the current control value by replacing it with the default value."""
|
||||||
@@ -324,3 +343,29 @@ class TextCtrl( BaseMaskedTextCtrl, MaskedEditAccessorsMixin ):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PreMaskedTextCtrl( BaseMaskedTextCtrl, MaskedEditAccessorsMixin ):
|
||||||
|
"""
|
||||||
|
This allows us to use XRC subclassing.
|
||||||
|
"""
|
||||||
|
# This should really be wx.EVT_WINDOW_CREATE but it is not
|
||||||
|
# currently delivered for native controls on all platforms, so
|
||||||
|
# we'll use EVT_SIZE instead. It should happen shortly after the
|
||||||
|
# control is created as the control is set to its "best" size.
|
||||||
|
_firstEventType = wx.EVT_SIZE
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pre = wx.PreTextCtrl()
|
||||||
|
self.PostCreate(pre)
|
||||||
|
self.Bind(self._firstEventType, self.OnCreate)
|
||||||
|
|
||||||
|
|
||||||
|
def OnCreate(self, evt):
|
||||||
|
self.Unbind(self._firstEventType)
|
||||||
|
self._PostInit()
|
||||||
|
|
||||||
|
i=0
|
||||||
|
## CHANGELOG:
|
||||||
|
## ====================
|
||||||
|
## Version 1.1
|
||||||
|
## 1. Added .SetFont() method that properly resizes control
|
||||||
|
## 2. Modified control to support construction via XRC mechanism.
|
||||||
|
@@ -59,7 +59,7 @@ Here's the API for TimeCtrl:
|
|||||||
<DL><PRE>
|
<DL><PRE>
|
||||||
<B>TimeCtrl</B>(
|
<B>TimeCtrl</B>(
|
||||||
parent, id = -1,
|
parent, id = -1,
|
||||||
<B>value</B> = '12:00:00 AM',
|
<B>value</B> = '00:00:00',
|
||||||
pos = wx.DefaultPosition,
|
pos = wx.DefaultPosition,
|
||||||
size = wx.DefaultSize,
|
size = wx.DefaultSize,
|
||||||
<B>style</B> = wxTE_PROCESS_TAB,
|
<B>style</B> = wxTE_PROCESS_TAB,
|
||||||
@@ -82,7 +82,10 @@ Here's the API for TimeCtrl:
|
|||||||
with SetValue() after instantiation of the control.)
|
with SetValue() after instantiation of the control.)
|
||||||
<DL><B>size</B>
|
<DL><B>size</B>
|
||||||
<DD>The size of the control will be automatically adjusted for 12/24 hour format
|
<DD>The size of the control will be automatically adjusted for 12/24 hour format
|
||||||
if wx.DefaultSize is specified.
|
if wx.DefaultSize is specified. NOTE: due to a problem with wx.DateTime, if the
|
||||||
|
locale does not use 'AM/PM' for its values, the default format will automatically
|
||||||
|
change to 24 hour format, and an AttributeError will be thrown if a non-24 format
|
||||||
|
is specified.
|
||||||
<DT><B>style</B>
|
<DT><B>style</B>
|
||||||
<DD>By default, TimeCtrl will process TAB events, by allowing tab to the
|
<DD>By default, TimeCtrl will process TAB events, by allowing tab to the
|
||||||
different cells within the control.
|
different cells within the control.
|
||||||
@@ -337,7 +340,7 @@ class TimeCtrl(BaseMaskedTextCtrl):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def __init__ (
|
def __init__ (
|
||||||
self, parent, id=-1, value = '12:00:00 AM',
|
self, parent, id=-1, value = '00:00:00',
|
||||||
pos = wx.DefaultPosition, size = wx.DefaultSize,
|
pos = wx.DefaultPosition, size = wx.DefaultSize,
|
||||||
fmt24hr=False,
|
fmt24hr=False,
|
||||||
spinButton = None,
|
spinButton = None,
|
||||||
@@ -348,6 +351,15 @@ class TimeCtrl(BaseMaskedTextCtrl):
|
|||||||
|
|
||||||
# set defaults for control:
|
# set defaults for control:
|
||||||
## dbg('setting defaults:')
|
## dbg('setting defaults:')
|
||||||
|
|
||||||
|
self.__fmt24hr = False
|
||||||
|
wxdt = wx.DateTimeFromDMY(1, 0, 1970)
|
||||||
|
if wxdt.Format('%p') != 'AM':
|
||||||
|
TimeCtrl.valid_ctrl_params['format'] = '24HHMMSS'
|
||||||
|
self.__fmt24hr = True
|
||||||
|
fmt24hr = True # force/change default positional argument
|
||||||
|
# (will countermand explicit set to False too.)
|
||||||
|
|
||||||
for key, param_value in TimeCtrl.valid_ctrl_params.items():
|
for key, param_value in TimeCtrl.valid_ctrl_params.items():
|
||||||
# This is done this way to make setattr behave consistently with
|
# This is done this way to make setattr behave consistently with
|
||||||
# "private attribute" name mangling
|
# "private attribute" name mangling
|
||||||
@@ -367,7 +379,6 @@ class TimeCtrl(BaseMaskedTextCtrl):
|
|||||||
kwargs['displaySeconds'] = True
|
kwargs['displaySeconds'] = True
|
||||||
|
|
||||||
# (handle positional arg (from original release) differently from rest of kwargs:)
|
# (handle positional arg (from original release) differently from rest of kwargs:)
|
||||||
self.__fmt24hr = False
|
|
||||||
if not kwargs.has_key('format'):
|
if not kwargs.has_key('format'):
|
||||||
if fmt24hr:
|
if fmt24hr:
|
||||||
if kwargs.has_key('displaySeconds') and kwargs['displaySeconds']:
|
if kwargs.has_key('displaySeconds') and kwargs['displaySeconds']:
|
||||||
@@ -449,7 +460,7 @@ class TimeCtrl(BaseMaskedTextCtrl):
|
|||||||
self.SetLimited(limited)
|
self.SetLimited(limited)
|
||||||
self.SetValue(value)
|
self.SetValue(value)
|
||||||
except:
|
except:
|
||||||
self.SetValue('12:00:00 AM')
|
self.SetValue('00:00:00')
|
||||||
|
|
||||||
if spinButton:
|
if spinButton:
|
||||||
self.BindSpinButton(spinButton) # bind spin button up/down events to this control
|
self.BindSpinButton(spinButton) # bind spin button up/down events to this control
|
||||||
@@ -472,6 +483,12 @@ class TimeCtrl(BaseMaskedTextCtrl):
|
|||||||
raise AttributeError('invalid keyword argument "%s"' % key)
|
raise AttributeError('invalid keyword argument "%s"' % key)
|
||||||
|
|
||||||
if key == 'format':
|
if key == 'format':
|
||||||
|
wxdt = wx.DateTimeFromDMY(1, 0, 1970)
|
||||||
|
if wxdt.Format('%p') != 'AM':
|
||||||
|
require24hr = True
|
||||||
|
else:
|
||||||
|
require24hr = False
|
||||||
|
|
||||||
# handle both local or generic 'maskededit' autoformat codes:
|
# handle both local or generic 'maskededit' autoformat codes:
|
||||||
if param_value == 'HHMMSS' or param_value == 'TIMEHHMMSS':
|
if param_value == 'HHMMSS' or param_value == 'TIMEHHMMSS':
|
||||||
self.__displaySeconds = True
|
self.__displaySeconds = True
|
||||||
@@ -487,6 +504,10 @@ class TimeCtrl(BaseMaskedTextCtrl):
|
|||||||
self.__fmt24hr = True
|
self.__fmt24hr = True
|
||||||
else:
|
else:
|
||||||
raise AttributeError('"%s" is not a valid format' % param_value)
|
raise AttributeError('"%s" is not a valid format' % param_value)
|
||||||
|
|
||||||
|
if require24hr and not self.__fmt24hr:
|
||||||
|
raise AttributeError('"%s" is an unsupported time format for the current locale' % param_value)
|
||||||
|
|
||||||
reset_format = True
|
reset_format = True
|
||||||
|
|
||||||
elif key in ("displaySeconds", "display_seconds") and not kwargs.has_key('format'):
|
elif key in ("displaySeconds", "display_seconds") and not kwargs.has_key('format'):
|
||||||
@@ -552,7 +573,7 @@ class TimeCtrl(BaseMaskedTextCtrl):
|
|||||||
self.SetLimited(limited)
|
self.SetLimited(limited)
|
||||||
self.SetValue(value)
|
self.SetValue(value)
|
||||||
except:
|
except:
|
||||||
self.SetValue('12:00:00 AM')
|
self.SetValue('00:00:00')
|
||||||
## dbg(indent=0)
|
## dbg(indent=0)
|
||||||
return {} # no arguments to return
|
return {} # no arguments to return
|
||||||
else:
|
else:
|
||||||
@@ -663,6 +684,11 @@ class TimeCtrl(BaseMaskedTextCtrl):
|
|||||||
## dbg('checkTime == len(value)?', valid)
|
## dbg('checkTime == len(value)?', valid)
|
||||||
|
|
||||||
if not valid:
|
if not valid:
|
||||||
|
# deal with bug/deficiency in wx.DateTime:
|
||||||
|
if wxdt.Format('%p') not in ('AM', 'PM') and checkTime in (5,8):
|
||||||
|
# couldn't parse the AM/PM field
|
||||||
|
raise ValueError('cannot convert string "%s" to valid time for the current locale; please use 24hr time instead' % value)
|
||||||
|
else:
|
||||||
## dbg(indent=0, suspend=0)
|
## dbg(indent=0, suspend=0)
|
||||||
raise ValueError('cannot convert string "%s" to valid time' % value)
|
raise ValueError('cannot convert string "%s" to valid time' % value)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user