baseSingleLineEditNumber.py (14772B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\carbonui\control\singlelineedits\baseSingleLineEditNumber.py 2 import blue 3 from carbon.client.script.environment.AudioUtil import PlaySound 4 from carbonui import uiconst 5 from carbonui.control.singlelineedits.baseSingleLineEdit import BaseSingleLineEdit 6 from carbonui.primitives.container import Container 7 from carbonui.uicore import uicore 8 from carbonui.control.buttonIcon import ButtonIcon 9 import eveLocalization 10 from localization import SYSTEM_LANGUAGE 11 from localization.formatters.numericFormatters import FormatNumeric 12 from menu import MenuLabel 13 import platform 14 import sys 15 import trinity 16 from uthread2 import start_tasklet 17 18 class BaseSingleLineEditNumbers(BaseSingleLineEdit): 19 default_name = 'BaseSingleLineEditNumbers' 20 default_minValue = None 21 default_maxValue = None 22 default_legalCharacters = '' 23 default_dataType = None 24 default_arrowIconColor = ButtonIcon.default_iconColor 25 default_arrowIconClass = None 26 default_arrowIconBlendMode = trinity.TR2_SBM_ADD 27 default_arrowIconGlowColor = None 28 default_arrowUseThemeColor = True 29 default_showNumericControls = True 30 HYPHEN = u'-' 31 32 def ApplyAttributes(self, attributes): 33 self.numericControlsCont = None 34 minValue = attributes.get('minValue', self.default_minValue) 35 maxValue = attributes.get('maxValue', self.default_maxValue) 36 self.dataType = attributes.get('dataType', self.default_dataType) 37 self.legalCharacters = attributes.get('legalCharacters', self.default_legalCharacters) 38 self.arrowIconColor = attributes.get('arrowIconColor', self.default_arrowIconColor) 39 self.arrowIconClass = attributes.get('arrowIconClass', self.default_arrowIconClass) 40 self.arrowIconBlendMode = attributes.get('arrowIconBlendMode', self.default_arrowIconBlendMode) 41 self.arrowIconGlowColor = attributes.get('arrowIconGlowColor', self.default_arrowIconGlowColor) 42 self.arrowUseThemeColor = attributes.get('arrowUseThemeColor', self.default_arrowUseThemeColor) 43 if minValue is None: 44 minValue = self.default_minValue 45 if maxValue is None: 46 maxValue = self.default_maxValue 47 self.SetMaxValue(maxValue) 48 self.SetMinValue(minValue) 49 super(BaseSingleLineEditNumbers, self).ApplyAttributes(attributes) 50 showNumericControls = attributes.get('showNumericControls', self.default_showNumericControls) 51 if showNumericControls: 52 self.ShowNumericControls() 53 54 def Insert(self, text): 55 if self.readonly or not text: 56 return 57 if not isinstance(text, basestring): 58 text = unichr(text) 59 text = text.replace(u'\r', u' ').replace(u'\n', u'') 60 text = self.FilterIllegalChars(text) 61 if not text: 62 return 63 if text == self.HYPHEN and self.minValue >= 0: 64 return 65 if self.GetSelectionBounds() != (None, None): 66 self.DeleteSelected(inserting=True) 67 caretIndex = self.caretIndex[0] 68 numSeparators = self.CountThousandSeparators() 69 caretIndex -= numSeparators 70 try: 71 self.ValidateNegativeInput(text, caretIndex) 72 except ValueError: 73 return 74 75 before = self.text[:caretIndex] 76 after = self.text[caretIndex:] 77 become = before + text + after 78 become = self.ValidateNewText(become) 79 if not become: 80 return 81 become, caretIndex = self.CheckForZeroInput(become, caretIndex) 82 self.autoselect = False 83 ret = self.CheckBounds(become, allowEmpty=bool(self.hintText), returnNoneIfOK=True) 84 if ret is not None: 85 if ret >= self.maxValue: 86 become = repr(ret) 87 self.SetText(become) 88 newCaretIndex = min(caretIndex + max(len(text), 1), len(become)) 89 newNumSeparators = self.CountThousandSeparators(newCaretIndex + numSeparators) 90 newCaretIndex += newNumSeparators 91 if ret is not None: 92 newCaretIndex = -1 93 self.caretIndex = self.GetCursorFromIndex(newCaretIndex) 94 self.OnTextChange() 95 96 def ValidateNewText(self, newText): 97 return newText 98 99 def CheckBounds(self, quantity, allowEmpty = True, returnNoneIfOK = False): 100 if allowEmpty and not quantity: 101 return quantity 102 if quantity == self.HYPHEN or not quantity: 103 quantity = 0 104 quantity = self.FilterIllegalChars(repr(quantity)) 105 minusIndex = repr(quantity).find(self.HYPHEN) 106 if minusIndex > 1: 107 return self.minValue 108 quantity = self.dataType(quantity) 109 if quantity > self.maxValue: 110 quantity = self.maxValue 111 elif quantity < self.minValue: 112 quantity = self.minValue 113 elif returnNoneIfOK: 114 return None 115 return quantity 116 117 def SetMaxValue(self, newMaxValue): 118 self.maxValue = self.dataType(min(newMaxValue, sys.maxsize)) 119 120 def SetMinValue(self, newMinValue): 121 self.minValue = self.dataType(max(newMinValue, -sys.maxsize)) 122 123 def FilterIllegalChars(self, text): 124 return filter(lambda x: x in self.legalCharacters, text) 125 126 def Copy(self, selectStart = None, selectEnd = None): 127 text = self.GetText() 128 if selectStart is not None and selectEnd is not None: 129 blue.pyos.SetClipboardData(text[selectStart:selectEnd]) 130 else: 131 start, end = self.GetSelectionBounds() 132 if not start and not end: 133 blue.pyos.SetClipboardData(text) 134 else: 135 numSeparators = self.CountThousandSeparators(startIndex=start[0], endIndex=end[0]) 136 blue.pyos.SetClipboardData(text[start[0]:end[0] - numSeparators]) 137 138 def CountThousandSeparators(self, endIndex = 0, startIndex = 0): 139 endIndex = endIndex if endIndex else self.caretIndex[0] 140 displayText = self.textLabel.GetText() 141 endIndex = min(endIndex, len(displayText)) 142 displayTextFromStartIndexToEndIndex = displayText[startIndex:endIndex] 143 return displayTextFromStartIndexToEndIndex.count(self.GetLocalizedThousandSeparator()) 144 145 def CheckForZeroInput(self, become, caretIndex): 146 allLettersAreZero = all((letter == '0' for letter in become)) 147 if allLettersAreZero: 148 become = '0' 149 caretIndex = 0 150 return (become, caretIndex) 151 152 def GetLocalizedThousandSeparator(self): 153 return eveLocalization.GetThousandSeparator(SYSTEM_LANGUAGE) 154 155 def OnUpKeyPressed(self): 156 self.ChangeNumericValue(1) 157 158 def OnDownKeyPressed(self): 159 super(BaseSingleLineEditNumbers, self).OnDownKeyPressed() 160 self.ChangeNumericValue(-1) 161 162 def Delete(self, direction = True): 163 if self.readonly: 164 return 165 oldCaretIndex = self.caretIndex[0] 166 indexWithoutSeparators = max(oldCaretIndex - self.CountThousandSeparators(endIndex=oldCaretIndex), 0) 167 if direction: 168 begin = indexWithoutSeparators 169 end = min(indexWithoutSeparators + 1, len(self.textLabel.text)) 170 newIndexWithoutSeparators = indexWithoutSeparators 171 else: 172 begin = max(indexWithoutSeparators - 1, 0) 173 end = indexWithoutSeparators 174 newIndexWithoutSeparators = max(indexWithoutSeparators - 1, 0) 175 newText = self.text[:begin] + self.text[end:] 176 self.SetText(newText) 177 numSeparators = self.CountThousandSeparators(endIndex=indexWithoutSeparators) 178 self.caretIndex = self.GetCursorFromIndex(newIndexWithoutSeparators + numSeparators) 179 self.OnTextChange() 180 181 def StripLeadingZeroesIfNecessary(self, newText): 182 if not newText or newText.startswith('0.') or newText.startswith('0L') or newText == '0': 183 return newText 184 stripped = newText.lstrip('0') 185 if stripped == 'L' or stripped == '': 186 return '0' 187 return stripped 188 189 def DeleteSelected(self, inserting = False): 190 if self.readonly: 191 return 192 start, end = self.GetSelectionBounds() 193 self.selFrom = self.selTo = None 194 self.RefreshSelectionDisplay() 195 separatorsTilStart = 0 196 if start[0]: 197 separatorsTilStart = self.CountThousandSeparators(endIndex=start[0]) 198 separatorsBetweenSelection = self.CountThousandSeparators(startIndex=start[0], endIndex=end[0]) 199 deleteFromIndex = max(start[0] - separatorsTilStart, 0) 200 deleteUntilIndex = min(end[0] - separatorsTilStart - separatorsBetweenSelection, len(self.text)) 201 newText = self.text[:deleteFromIndex] + self.text[deleteUntilIndex:] 202 self.SetText(newText, format=not inserting) 203 newSeparatorsTilStart = self.CountThousandSeparators(endIndex=deleteFromIndex) 204 newCaretIndex = start[0] - separatorsTilStart + newSeparatorsTilStart 205 self.caretIndex = self.GetCursorFromIndex(newCaretIndex) 206 self.OnTextChange() 207 208 def SetText(self, newText, format = True): 209 return super(BaseSingleLineEditNumbers, self).SetText(newText) 210 211 def FormatNumeric(self, otext, useGrouping = True, decimalPlaces = 0, leadingZeroes = 0): 212 if not otext: 213 return '' 214 else: 215 try: 216 number = self.dataType(otext or 0) 217 except ValueError: 218 return otext 219 220 return FormatNumeric(number, useGrouping=useGrouping, decimalPlaces=decimalPlaces, leadingZeroes=leadingZeroes) 221 222 def SetValue(self, text, docallback = 1): 223 if text == self.HYPHEN: 224 text = self.minValue 225 text = self.dataType(text) if text else text 226 text = self.CheckBounds(text, allowEmpty=bool(self.hintText)) 227 super(BaseSingleLineEditNumbers, self).SetValue(text, docallback) 228 229 def ShowNumericControls(self): 230 if self.numericControlsCont: 231 return 232 self.numericControlsCont = Container(name='numericControlsCont', parent=self, align=uiconst.TORIGHT, idx=0, width=10, padding=(0, 1, 8, 1), opacity=0.75, state=uiconst.UI_HIDDEN if self.readonly else uiconst.UI_NORMAL) 233 self.upButton = ButtonIcon(name='upButton', parent=self.numericControlsCont, align=uiconst.CENTER, pos=(0, -4, 9, 9), iconSize=7, texturePath='res:/UI/Texture/Shared/up.png', soundClick=uiconst.SOUND_VALUECHANGE_TICK, iconColor=self.arrowIconColor, glowColor=self.arrowIconGlowColor, iconClass=self.arrowIconClass, useThemeColor=self.arrowUseThemeColor, iconBlendMode=self.arrowIconBlendMode) 234 self.upButton.OnMouseDown = self.OnNumericUpButtonMouseDown 235 self.upButton.OnMouseUp = self.OnNumericUpButtonMouseUp 236 self.downButton = ButtonIcon(name='downButton', parent=self.numericControlsCont, align=uiconst.CENTER, pos=(0, 4, 9, 9), iconSize=7, texturePath='res:/UI/Texture/Shared/down.png', soundClick=uiconst.SOUND_VALUECHANGE_TICK, iconColor=self.arrowIconColor, glowColor=self.arrowIconGlowColor, iconClass=self.arrowIconClass, useThemeColor=self.arrowUseThemeColor, iconBlendMode=self.arrowIconBlendMode) 237 self.downButton.OnMouseDown = self.OnNumericDownButtonMouseDown 238 self.downButton.OnMouseUp = self.OnNumericDownButtonMouseUp 239 240 def OnNumericUpButtonMouseDown(self, *args): 241 ButtonIcon.OnMouseDown(self.upButton, *args) 242 self.updateNumericInputThread = start_tasklet(self.UpdateNumericInputThread, 1) 243 244 def OnNumericDownButtonMouseDown(self, *args): 245 ButtonIcon.OnMouseDown(self.downButton, *args) 246 self.updateNumericInputThread = start_tasklet(self.UpdateNumericInputThread, -1) 247 248 def KillNumericInputThread(self): 249 if self.updateNumericInputThread: 250 self.updateNumericInputThread.kill() 251 self.updateNumericInputThread = None 252 253 def OnNumericUpButtonMouseUp(self, *args): 254 ButtonIcon.OnMouseUp(self.upButton, *args) 255 self.KillNumericInputThread() 256 257 def OnNumericDownButtonMouseUp(self, *args): 258 ButtonIcon.OnMouseUp(self.downButton, *args) 259 self.KillNumericInputThread() 260 261 def UpdateNumericInputThread(self, diff): 262 sleepTime = 500 263 while uicore.uilib.leftbtn: 264 self.ChangeNumericValue(diff) 265 blue.synchro.SleepWallclock(sleepTime) 266 sleepTime -= 0.5 * sleepTime 267 sleepTime = max(10, sleepTime) 268 269 def OnMouseWheel(self, *args): 270 if self.readonly: 271 return 272 PlaySound(uiconst.SOUND_VALUECHANGE_TICK) 273 if platform.system() == 'Darwin': 274 self.ChangeNumericValue(uicore.uilib.dz / 120.0) 275 else: 276 self.ChangeNumericValue((uicore.uilib.dz / 120) ** 3) 277 278 def ChangeNumericValue(self, val): 279 self.BlinkButtons(val) 280 self.ClampMinMaxValue(val) 281 282 def GetValue(self, *args, **kwargs): 283 if not self.text: 284 return self.minValue or 0 285 elif self.text == self.HYPHEN: 286 return 0 287 else: 288 try: 289 value = self.dataType(self.text) 290 except ValueError: 291 return 0 292 293 return value 294 295 def ValidateNegativeInput(self, text, caretIndex): 296 newvalue = self.text[:caretIndex] + text + self.text[caretIndex:] 297 if text == self.HYPHEN: 298 if newvalue != self.HYPHEN: 299 newvalue = self.FilterIllegalChars(newvalue) 300 try: 301 self.dataType(newvalue) 302 except ValueError: 303 sys.exc_clear() 304 raise ValueError 305 306 elif abs(self.dataType(newvalue)) >= sys.maxsize: 307 raise ValueError 308 309 def BlinkButtons(self, val): 310 if not self.numericControlsCont: 311 return 312 if val > 0: 313 self.upButton.Blink(0.2) 314 else: 315 self.downButton.Blink(0.2) 316 317 def ClampMinMaxValue(self, change = 0): 318 current = 0 319 try: 320 current = self.dataType(self.text) 321 except ValueError: 322 sys.exc_clear() 323 324 newText = repr(current + change) 325 text = self.CheckBounds(newText) 326 self.SetText(text) 327 self.SendCaretToEnd() 328 self.ClearSelection() 329 self.OnTextChange() 330 331 def GetMenu(self): 332 m = super(BaseSingleLineEditNumbers, self).GetMenu() 333 if self.displayHistory: 334 m += [(MenuLabel('/Carbon/UI/Controls/Common/ClearHistory'), self.ClearHistory, (None,))] 335 return m 336 337 def OnKillFocus(self, *args): 338 ret = self.CheckBounds(self.text, allowEmpty=bool(self.hintText), returnNoneIfOK=True) 339 if ret is not None: 340 if ret <= self.minValue: 341 self.SetValue(self.minValue) 342 super(BaseSingleLineEditNumbers, self).OnKillFocus(*args)