Ticket #1166: dComboBox.py

File dComboBox.py, 8.0 kB (added by johnf, 4 years ago)
Line 
1 # -*- coding: utf-8 -*-
2 import wx, dabo, dabo.ui
3 if __name__ == "__main__":
4     dabo.ui.loadUI("wx")
5 import dControlItemMixin as dcm
6 import dabo.dEvents as dEvents
7 from dabo.dLocalize import _
8 from dabo.ui import makeDynamicProperty
9
10
11 class dComboBox(dcm.dControlItemMixin, wx.ComboBox):
12     """Creates a combobox, which combines a dropdown list with a textbox.
13     
14     The user can choose an item in the dropdown, or enter freeform text.
15     """
16     def __init__(self, parent, properties=None, attProperties=None, *args, **kwargs):
17         self._baseClass = dComboBox
18         self._choices = []
19         self._userVal = False
20         # Used to force the case of entered text
21         self._forceCase = None
22         self._inForceCase = False
23         self._textLength = None
24         # Flag for appending items when the user presses 'Enter'
25         self._appendOnEnter = False
26         # Holds the text to be appended
27         self._textToAppend = ""
28
29         preClass = wx.PreComboBox
30         dcm.dControlItemMixin.__init__(self, preClass, parent, properties, attProperties, *args, **kwargs)
31
32
33     def _preInitUI(self, kwargs):
34         style = kwargs.get("style", 0)
35         style |= wx.PROCESS_ENTER
36         kwargs["style"] = style
37         return kwargs
38
39
40     def _initEvents(self):
41         super(dComboBox, self)._initEvents()
42         self.Bind(wx.EVT_COMBOBOX, self.__onComboBox)
43         self.Bind(wx.EVT_TEXT_ENTER, self.__onTextBox)
44            
45    
46     def __onComboBox(self, evt):
47         self._userVal = False
48         evt.Skip()
49         self._onWxHit(evt)
50        
51        
52     def __onTextBox(self, evt):
53         self._userVal = True
54         evt.Skip()
55         if self.AppendOnEnter:
56             txt = evt.GetString()
57             if txt not in self.Choices:
58                 self._textToAppend = txt
59                 if self.beforeAppendOnEnter() is not False:
60                     if self._textToAppend:
61                         self.appendItem(self._textToAppend, select=True)
62                         self.afterAppendOnEnter()
63         self.raiseEvent(dabo.dEvents.Hit, evt)
64    
65
66     def __onKeyChar(self, evt):
67         """This handles KeyChar events when ForceCase is set to a non-empty value."""
68         if not self:
69             # The control is being destroyed
70             return
71         keyChar = evt.keyChar
72         if keyChar is not None and (keyChar.isalnum()
73                 or keyChar in """,./<>?;':"[]\\{}|`~!@#$%%^&*()-_=+"""):
74             dabo.ui.callAfter(self._checkForceCase)
75             dabo.ui.callAfter(self._checkTextLength)
76
77
78     def _checkTextLength(self):
79         """If the TextLength property is set, checks the current value of the control
80         and truncates it if too long"""
81         if not self:
82             # The control is being destroyed
83             return
84         if not isinstance(self.GetValue(), basestring):
85             #Don't bother if it isn't a string type
86             return
87         length = self.TextLength
88         if not length:
89             return
90         val = self.GetValue()
91         if len(val) > length:
92             dabo.ui.beep()
93             self.SetValue(val[:length])
94    
95
96     def _checkForceCase(self):
97         """If the ForceCase property is set, casts the current value of the control
98         to the specified case.
99         """
100         if not self:
101             # The control is being destroyed
102             return
103         if not isinstance(self.GetValue(), basestring):
104             # Don't bother if it isn't a string type
105             return
106         case = self.ForceCase
107         if not case:
108             return
109         if case == "upper":
110             self.SetValue(self.GetValue().upper())
111         elif case == "lower":
112             self.SetValue(self.GetValue().lower())
113         elif case == "title":
114             self.SetValue(self.GetValue().title())
115
116
117     def beforeAppendOnEnter(self):
118         """Hook method that is called when user-defined text is entered
119         into the combo box and Enter is pressed (when self.AppendOnEnter
120         is True). This gives the programmer the ability to interact with such
121         events, and optionally prevent them from happening. Returning
122         False will prevent the append from happening.
123         
124         The text value to be appended is stored in self._textToAppend. You
125         may modify this value (e.g., force to upper case), or delete it entirely
126         (e.g., filter out obscenities and such). If you set self._textToAppend
127         to an empty string, nothing will be appended. So this 'before' hook
128         gives you two opportunities to prevent the append: return a non-
129         empty value, or clear out self._textToAppend.
130         """
131         pass
132        
133        
134     def afterAppendOnEnter(self):
135         """Hook method that provides a means to interact with the newly-
136         changed list of items after a new item has been added by the user
137         pressing Enter, but before control returns to the program.
138         """
139         pass
140        
141        
142     # Property get/set/del methods follow. Scroll to bottom to see the property
143     # definitions themselves.
144     def _getAppendOnEnter(self):
145         return self._appendOnEnter
146
147     def _setAppendOnEnter(self, val):
148         if self._constructed():
149             self._appendOnEnter = val
150         else:
151             self._properties["AppendOnEnter"] = val
152
153
154     def _getForceCase(self):
155         return self._forceCase
156    
157     def _setForceCase(self, val):
158         if self._constructed():
159             if val is None:
160                 valKey = None
161             else:
162                 valKey = val[0].upper()
163             self._forceCase = {"U": "upper", "L": "lower", "T": "title", None: None,
164                     "None": None}.get(valKey)
165             self._checkForceCase()
166             self.unbindEvent(dEvents.KeyChar, self.__onKeyChar)
167             if self._forceCase or self._textLength:
168                 self.bindEvent(dEvents.KeyChar, self.__onKeyChar)
169         else:
170             self._properties["ForceCase"] = val
171    
172    
173     def _getTextLength(self):
174         return self._textLength
175    
176     def _setTextLength(self, val):
177         if self._constructed():
178             if val == None:
179                 self._textLength = None
180             else:
181                 val = int(val)
182                 if val < 1:
183                     raise ValueError, 'TextLength must be a positve Integer'
184                 self._textLength = val
185             self._checkTextLength()
186            
187             self.unbindEvent(dEvents.KeyChar, self.__onKeyChar)
188             if self._forceCase or self._textLength:
189                 self.bindEvent(dEvents.KeyChar, self.__onKeyChar)
190         else:
191             self._properties["TextLength"] = val
192    
193    
194     def _getUserValue(self):
195         if self._userVal:
196             return self.GetValue()
197         else:
198             return self.GetStringSelection()
199                
200     def _setUserValue(self, value):
201         if self._constructed():
202             self.SetValue(value)
203             # don't call _afterValueChanged(), because value tracks the item in the list,
204             # not the displayed value. User code can query UserValue and then decide to
205             # add it to the list, if appropriate.
206         else:
207             self._properties["UserValue"] = value
208    
209    
210     AppendOnEnter = property(_getAppendOnEnter, _setAppendOnEnter, None,
211             _("""Flag to determine if user-entered text is appended when they
212             press 'Enter'  (bool)"""))
213    
214     ForceCase = property(_getForceCase, _setForceCase, None,
215             _("""Determines if we change the case of entered text. Possible values are:
216                 None, "" (empty string): No changes made (default)
217                 "Upper": FORCE TO UPPER CASE
218                 "Lower": force to lower case
219                 "Title": Force To Title Case
220             These can be abbreviated to "u", "l" or "t"  (str)"""))
221    
222     TextLength = property(_getTextLength, _setTextLength, None,
223             _("""The maximum length the entered text can be. (int)"""))
224    
225     UserValue = property(_getUserValue, _setUserValue, None,
226             _("""Specifies the text displayed in the textbox portion of the ComboBox.
227             
228             String. Read-write at runtime.
229             
230             UserValue can differ from StringValue, which would mean that the user
231             has typed in arbitrary text. Unlike StringValue, PositionValue, and
232             KeyValue, setting UserValue does not change the currently selected item
233             in the list portion of the ComboBox."""))
234        
235        
236     DynamicUserValue = makeDynamicProperty(UserValue)
237
238
239
240 class _dComboBox_test(dComboBox):
241     def initProperties(self):
242         self.setup()
243         self.AppendOnEnter = True
244        
245     def setup(self):
246         # Simulating a database:
247         wannabeCowboys = ({"lname": "Reagan", "fname": "Ronald", "iid": 42},
248             {"lname": "Bush", "fname": "George W.", "iid": 23})
249            
250         choices = []
251         keys = {}
252         for wannabe in wannabeCowboys:
253             choices.append("%s %s" % (wannabe['fname'], wannabe['lname']))
254             keys[wannabe["iid"]] = len(choices) - 1
255            
256         self.Choices = choices
257         self.Keys = keys
258         self.ValueMode = 'key'
259    
260    
261     def beforeAppendOnEnter(self):
262         txt = self._textToAppend.strip().lower()
263         if txt == "dabo":
264             print _("Attempted to add Dabo to the list!!!")
265             return False
266         elif txt.find("nixon") > -1:
267             self._textToAppend = "Tricky Dick"
268        
269        
270     def onHit(self, evt):
271         print "KeyValue: ", self.KeyValue
272         print "PositionValue: ", self.PositionValue
273         print "StringValue: ", self.StringValue
274         print "Value: ", self.Value
275         print "UserValue: ", self.UserValue
276        
277            
278 if __name__ == "__main__":
279     import test
280     test.Test().runTest(_dComboBox_test)