| 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) |
|---|