editPlainText.py (94642B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\carbonui\control\editPlainText.py 2 import logging 3 import re 4 import sys 5 import types 6 from collections import defaultdict 7 import threadutils 8 from carbon.client.script.environment.AudioUtil import PlaySound 9 from carbon.common.script.util import mathUtil 10 from carbonui.control.scrollentries import ScrollEntryNode, SE_BaseClassCore, SE_TextlineCore 11 from carbonui.control.label import LabelOverride as Label 12 from carbonui.control.singlelineedits.caret import Caret 13 from carbonui.control.link import Link 14 from carbonui.fontconst import DEFAULT_FONTSIZE 15 from carbonui.text.color import TextColor 16 from carbonui.text.settings import check_convert_font_size 17 from carbonui.primitives.container import Container 18 from carbonui.primitives.fill import Fill 19 from carbonui.primitives.sprite import Sprite 20 from carbon.common.script.util.commonutils import StripTags 21 import blue 22 import telemetry 23 import uthread 24 import eveLocalization 25 import log 26 import carbonui.const as uiconst 27 import trinity 28 from carbon.common.script.util.timerstuff import AutoTimer 29 from carbonui.util.various_unsorted import GetBrowser, GetClipboardData, StringColorToHex 30 from carbonui.uicore import uicore 31 from eve.client.script.ui.control.eveScroll import Scroll 32 from menu import MenuLabel 33 from player_messaging.client.ui_message import prompt_player_fsd_dialog 34 logger = logging.getLogger(__name__) 35 LINESPLIT = re.compile('<br>|<br />|<br/>|\r\n|\n') 36 BOOLTAGS = [('loc', 'loc'), 37 ('b', 'bold'), 38 ('i', 'italic'), 39 ('u', 'underline')] 40 VALUETAGS = [('url', None), ('fontsize', 'defaultFontSize'), ('color', 'defaultFontColor')] 41 HTMLCONVERSION = {'fontsize': 'font size', 42 '/fontsize': '/font', 43 'color': 'font color', 44 '/color': '/font', 45 'url': 'a href', 46 '/url': '/a'} 47 HTMLFONTTAG = (('fontsize', 'size'), ('color', 'color')) 48 ENDLINKCHARS = u' ,' 49 TEXTSIDEMARGIN = 0 50 TEXTLINEMARGIN = 0 51 52 class EditPlainTextCore(Scroll): 53 default_name = 'edit_plaintext' 54 default_align = uiconst.TOTOP 55 default_height = 100 56 default_maxLength = None 57 default_readonly = False 58 default_showattributepanel = 0 59 default_setvalue = '' 60 default_allowFormatChanges = True 61 default_fontsize = None 62 default_fontStyle = None 63 default_fontFamily = None 64 default_fontPath = None 65 default_fontcolor = TextColor.NORMAL 66 default_linkStyle = uiconst.LINKSTYLE_REGULAR 67 default_hintText = '' 68 default_ignoreTags = False 69 default_innerPadding = 8 70 _autoSize = False 71 72 def ApplyAttributes(self, attributes): 73 if self.default_fontsize is None: 74 self.default_fontsize = DEFAULT_FONTSIZE 75 self.readonly = attributes.get('readonly', self.default_readonly) 76 if self.readonly: 77 attributes['hasUnderlay'] = False 78 super(EditPlainTextCore, self).ApplyAttributes(attributes) 79 self.sr.scrollTimer = None 80 self.keybuffer = [] 81 self._activeParams = None 82 self._activeNode = None 83 self._selecting = 0 84 self._showCursor = True 85 self._maxGlobalCursorIndex = 0 86 self._currentLen = None 87 self.sr.paragraphs = [] 88 self.globalSelectionRange = (None, None) 89 self.globalSelectionInitpos = None 90 self.globalCursorPos = 0 91 self.OnReturn = None 92 self.resizeTasklet = None 93 self.updating = 0 94 self.readonly = attributes.get('readonly', self.default_readonly) 95 self.autoScrollToBottom = 0 96 self.defaultFontStyle = attributes.get('fontStyle', self.default_fontStyle) 97 self.defaultFontFamily = attributes.get('fontFamily', self.default_fontFamily) 98 self.defaultFontPath = attributes.get('fontPath', self.default_fontPath) 99 self.defaultFontSize = attributes.get('fontsize', self.default_fontsize) 100 self.defaultFontSize = check_convert_font_size(self.defaultFontSize) 101 self.defaultFontColor = attributes.get('fontcolor', self.default_fontcolor) 102 self.linkStyle = attributes.get('linkStyle', self.default_linkStyle) 103 self.ignoreTags = attributes.get('ignoreTags', self.default_ignoreTags) 104 self.allowFormatChanges = attributes.get('allowFormatChanges', self.default_allowFormatChanges) 105 self.sr.content.GetMenu = self.GetMenuDelegate 106 self.sr.content.OnKeyDown = self.OnKeyDown 107 self.sr.content.OnDropData = self.OnContentDropData 108 self.sr.content.OnMouseDown = self.OnMouseDown 109 self.sr.content.OnMouseUp = self.OnMouseUp 110 self.sr.content.cursor = uiconst.UICURSOR_IBEAM 111 self._hintLabel = Label(parent=self.sr.clipper, align=uiconst.TOTOP, padding=6, state=uiconst.UI_DISABLED) 112 maxLength = attributes.get('maxLength', self.default_maxLength) 113 self.SetMaxLength(maxLength) 114 self.counterMax = attributes.get('counterMax', maxLength) 115 self.countWithTags = attributes.get('countWithTags', True) 116 self.customCharCounter = attributes.get('customCounter') 117 self.allowTruncating = attributes.get('allowTruncating', self.countWithTags) 118 showattributepanel = attributes.get('showattributepanel', self.default_showattributepanel) 119 if showattributepanel: 120 self.ShowAttributePanel() 121 self.SetHintText(attributes.get('hintText', self.default_hintText)) 122 setvalue = attributes.get('setvalue', self.default_setvalue) 123 self.SetValue(setvalue) 124 125 def DisableScrolling(self): 126 self.scrollEnabled = False 127 128 def EnableAutoSize(self): 129 self._autoSize = True 130 131 def CreateNode(self): 132 entry = ScrollEntryNode(decoClass=SE_EditTextlineCore, SelectionHandler=self) 133 return entry 134 135 def Close(self, *args, **kwds): 136 try: 137 for glyphString in self.sr.paragraphs: 138 glyphString.Reset() 139 140 self.resizeTasklet = None 141 except Exception as e: 142 logger.error(e) 143 144 super(EditPlainTextCore, self).Close(*args, **kwds) 145 146 def ShowAttributePanel(self): 147 if not self.readonly: 148 from carbonui.control.fontAttributePanel import FontAttributePanel 149 self.sr.attribPanel = FontAttributePanel(align=uiconst.TOTOP, padBottom=2, parent=self, idx=0, state=uiconst.UI_PICKCHILDREN, counterMax=self.counterMax, countWithTags=self.countWithTags, padding=(4, 0, 4, 0)) 150 151 def DoFontChange(self, *args): 152 self.DoSizeUpdateDelayed() 153 154 def IsLoading(self): 155 return self._loading 156 157 def CheckHintText(self): 158 if self._hintText: 159 self._hintLabel.fontsize = self.defaultFontSize 160 self._hintLabel.fontColor = self.defaultFontColor 161 self._hintLabel.opacity = 0.3 162 self._hintLabel.text = self._hintText 163 else: 164 self._hintLabel.text = '' 165 if self.HasText(): 166 self._hintLabel.display = False 167 else: 168 self._hintLabel.display = True 169 170 def SetHintText(self, hint): 171 self._hintText = hint 172 self.CheckHintText() 173 174 @telemetry.ZONE_METHOD 175 def SetValue(self, text, scrolltotop = 0, cursorPos = None, preformatted = 0, html = 1, fontColor = None): 176 if self.destroyed: 177 return 178 self._ResetCurrentLen() 179 text = text or '' 180 if not self.IsEnoughRoomForSet(text): 181 if self.allowTruncating: 182 text = text[:self.maxletters] 183 else: 184 return 185 self.GetAbsolute() 186 self._activeParams = None 187 newGS = uicore.font.GetGlyphString() 188 for glyphString in self.sr.paragraphs: 189 glyphString.Reset() 190 191 self.sr.paragraphs = [newGS] 192 self.LoadContent(contentList=[]) 193 self.UpdateGlyphString(newGS) 194 self.SetSelectionRange(None, None, updateCursor=False) 195 self.SetCursorPos(0) 196 self.SetSelectionInitPos(self.globalCursorPos) 197 for tag, paramName in BOOLTAGS: 198 setattr(self, 'tagStack_%s' % tag, []) 199 200 for paramName, defaultAttrName in VALUETAGS: 201 setattr(self, 'tagStack_%s' % paramName, []) 202 203 self._activeParams = None 204 scrollTo = None 205 if scrolltotop: 206 scrollTo = 0.0 207 elif self.autoScrollToBottom and self.GetScrollProportion() == 0.0: 208 scrollTo = 1.0 209 self.InsertText(text) 210 if not self.readonly: 211 self.SetCursorPos(cursorPos or 0) 212 if scrollTo is not None: 213 self.ScrollToProportion(scrollTo) 214 self.UpdateCharacterCounter() 215 216 def Clear(self): 217 self.SetText('') 218 219 @telemetry.ZONE_METHOD 220 def InsertText(self, text): 221 if self.destroyed: 222 return 223 text = text or '' 224 if not StripTags(text): 225 text = '' 226 if text.find('<h') != -1: 227 text = text.replace('<h1>', '<br><fontsize=28><b>').replace('</h1>', '</b></fontsize><br>') 228 text = text.replace('<h2>', '<br><fontsize=24><b>').replace('</h2>', '</b></fontsize><br>') 229 text = text.replace('<h3>', '<br><fontsize=20><b>').replace('</h3>', '</b></fontsize><br>') 230 text = text.replace('<h4>', '<br><fontsize=18><b>').replace('</h4>', '</b></fontsize><br>') 231 text = text.replace('<h5>', '<br><fontsize=16><b>').replace('</h5>', '</b></fontsize><br>') 232 text = text.replace('<h6>', '<br><fontsize=14><b>').replace('</h6>', '</b></fontsize><br>') 233 text = text.replace('\t', ' ') 234 lines = LINESPLIT.split(text) 235 node = self.GetActiveNode() 236 if node is None: 237 return 238 initCursor = self.globalCursorPos 239 advance = 0 240 stackCursorIndex = self.globalCursorPos - node.startCursorIndex + node.stackCursorIndex 241 glyphString = node.glyphString 242 nodesByGlyphStringIDs = self.GetDictNodesByGlyphStringIDs() 243 for lineIdx, line in enumerate(lines): 244 text = line 245 if not self.ignoreTags and line.find('<') > -1 and line.find('>') > -1: 246 for each in line.split(u'>'): 247 texttag = each.split(u'<', 1) 248 if len(texttag) == 1: 249 text, tag = self.Encode(texttag[0]), None 250 else: 251 text, tag = self.Encode(texttag[0]), texttag[1] 252 params = self.GetFontParams() 253 self.InsertToGlyphString(glyphString, params, text, stackCursorIndex) 254 stackCursorIndex += len(text) 255 advance += len(text) 256 if tag: 257 self.ParseTag(tag) 258 259 else: 260 text = self.Encode(text) 261 self.InsertToGlyphString(glyphString, self.GetFontParams(), text, stackCursorIndex) 262 stackCursorIndex += len(text) 263 advance += len(text) 264 gsNodes = self.UpdateGlyphString(glyphString, 0, 0, nodesByGlyphstringIDs=nodesByGlyphStringIDs) 265 if lineIdx != len(lines) - 1: 266 newGS = uicore.font.GetGlyphString() 267 gsIndex = self.GetGlyphStringIndex(glyphString) 268 self.InsertNewGlyphString(newGS, gsIndex + 1, gsNodes[-1].idx + 1, nodesByGlyphStringIDs) 269 glyphString = newGS 270 stackCursorIndex = len(newGS) 271 advance += 1 272 273 self.UpdatePosition() 274 self.SetCursorPos(initCursor + advance) 275 self.CheckHintText() 276 277 SetText = SetValue 278 279 def GetDictNodesByGlyphStringIDs(self): 280 nodesByGlyphstringIDs = defaultdict(list) 281 for node in self.sr.nodes: 282 glyphStringID = id(node.glyphString) 283 nodesByGlyphstringIDs[glyphStringID].append(node) 284 285 return nodesByGlyphstringIDs 286 287 def GetNodesByGlyphStringID(self, glyphStringID, nodesByGlyphstringIDs): 288 nodes = nodesByGlyphstringIDs.get(glyphStringID, []) 289 return sorted(nodes, key=lambda x: x.idx) 290 291 def Encode(self, text): 292 return text.replace(u'>', u'>').replace(u'<', u'<').replace(u'&', u'&').replace(u'>', u'>').replace(u'<', u'<') 293 294 def Decode(self, text): 295 return text.replace(u'&', u'&').replace(u'<', u'<').replace(u'>', u'>') 296 297 def ParseTag(self, tag): 298 orgtag = tag 299 tag = tag.replace('"', '') 300 tag = tag.replace("'", '') 301 tag = tag.lower() 302 if tag.startswith('font '): 303 fontcolor = self.GetTagValue('color=', tag) 304 if fontcolor: 305 self.ParseTag('color=' + fontcolor) 306 fontsize = self.GetTagValue('size=', tag) 307 if fontsize: 308 self.ParseTag('fontsize=' + fontsize) 309 tagStack = getattr(self, 'tagStack_font', []) 310 tagStack.append(tag) 311 setattr(self, 'tagStack_font', tagStack) 312 return 313 if tag == '/font': 314 tagStack = getattr(self, 'tagStack_font', []) 315 if tagStack: 316 closeFontTag = tagStack.pop() 317 fontcolor = self.GetTagValue('color=', closeFontTag) 318 if fontcolor: 319 self.ParseTag('/color') 320 fontsize = self.GetTagValue('size=', closeFontTag) 321 if fontsize: 322 self.ParseTag('/fontsize') 323 return 324 tag = tag.replace('a href=', 'url=').replace('/a', '/url').replace('bold', 'b').replace('/bold', '/b').replace('italic', 'i').replace('/italic', '/i') 325 if tag[0] == '/': 326 stackName = tag[1:] 327 elif '=' in tag: 328 stackName = tag.split('=')[0] 329 else: 330 stackName = tag 331 tagStack = getattr(self, 'tagStack_%s' % stackName, []) 332 if tag[0] == '/': 333 if len(tagStack): 334 tagStack.pop() 335 elif tag.startswith(u'color='): 336 colorSyntax = tag[6:] 337 color = StringColorToHex(colorSyntax) 338 if color: 339 col = mathUtil.LtoI(long(color, 0)) 340 tagStack.append(col) 341 else: 342 hexColor = colorSyntax.replace('#', '0x') 343 try: 344 col = mathUtil.LtoI(long(hexColor, 0)) 345 if isinstance(col, long): 346 raise ValueError 347 tagStack.append(col) 348 except ValueError: 349 if type(self.defaultFontColor) is types.IntType: 350 tagStack.append(-1) 351 else: 352 c = trinity.TriColor(*self.defaultFontColor) 353 tagStack.append(c.AsInt()) 354 355 elif '=' in tag: 356 value = orgtag.split('=') 357 value = '='.join(value[1:]) 358 if not value: 359 return 360 if value[0] == '"': 361 value = value[1:] 362 if value[-1] == '"': 363 value = value[:-1] 364 try: 365 addVal = int(value) 366 except: 367 addVal = value 368 369 tagStack.append(addVal) 370 else: 371 tagStack.append(1) 372 setattr(self, 'tagStack_%s' % stackName, tagStack) 373 374 def GetTagValue(self, tagtofind, tagstring): 375 start = tagstring.find(tagtofind) 376 if start != -1: 377 end = tagstring.find(' ', start) 378 if end == -1: 379 end = tagstring.find('>', start) 380 if end == -1: 381 end = len(tagstring) 382 return tagstring[start + len(tagtofind):end] 383 384 def GetColorSyntax(self, color, html): 385 if type(color) is types.IntType: 386 c = trinity.TriColor() 387 c.FromInt(color) 388 color = (c.r, 389 c.g, 390 c.b, 391 c.a) 392 if html: 393 colorString = '"#%02x%02x%02x%02x"' % (color[3] * 255, 394 color[0] * 255, 395 color[1] * 255, 396 color[2] * 255) 397 else: 398 colorString = '0x%02x%02x%02x%02x' % (color[3] * 255, 399 color[0] * 255, 400 color[1] * 255, 401 color[2] * 255) 402 return colorString 403 404 def GetValue(self, html = 1): 405 return self.GetValueForParagraphs(html=html, paragraphs=self.sr.paragraphs) 406 407 @telemetry.ZONE_METHOD 408 def GetValueForParagraphs(self, html = 1, paragraphs = ()): 409 410 def FormatValue(value, html): 411 if html: 412 return '"%s"' % value 413 return value 414 415 def FormatTag(fmttag, html): 416 if html and fmttag in HTMLCONVERSION: 417 return HTMLCONVERSION[fmttag] 418 return fmttag 419 420 defaultColorValue = self.GetColorSyntax(self.defaultFontColor, html) 421 defaultFontSizeValue = FormatValue(self.defaultFontSize, html) 422 defaultHtmlFontTagCombined = '<font size=%s color=%s>' % (defaultFontSizeValue, defaultColorValue) 423 defaultAdded = False 424 tagStacks = {} 425 for tag, defaultAttrName in VALUETAGS: 426 if defaultAttrName: 427 tagStacks[tag] = [getattr(self, defaultAttrName, None)] 428 else: 429 tagStacks[tag] = [None] 430 431 tagStacks['fontCombined'] = [defaultHtmlFontTagCombined] 432 for tag, paramName in BOOLTAGS: 433 tagStacks[tag] = [False] 434 435 lastParams = None 436 retString = '' 437 addBreak = 0 438 for glyphString in paragraphs: 439 for charData in glyphString: 440 advance, align, color, sbit, char, asc, des, params = charData 441 if lastParams is not params: 442 lastParams = params 443 for paramName, defaultAttrName in VALUETAGS: 444 if html and paramName in ('font', 'color', 'fontsize'): 445 continue 446 if paramName == 'color' and tagStacks['url'][-1]: 447 continue 448 currentStack = tagStacks[paramName] 449 currentStackValue = currentStack[-1] 450 currentParamValue = params.get(paramName, None) 451 if currentParamValue != currentStackValue: 452 if len(currentStack) > 1: 453 retString += '<%s>' % FormatTag('/%s' % paramName, html) 454 currentStack.pop() 455 456 for tag, paramName in BOOLTAGS: 457 tagStack = tagStacks[tag] 458 paramValue = params.get(paramName, False) 459 if paramValue != tagStack[-1]: 460 if len(tagStack) > 1: 461 retString += '<%s>' % FormatTag('/%s' % tag, html) 462 tagStack.pop() 463 464 while addBreak > 0: 465 retString += '<br>' 466 addBreak -= 1 467 468 if html: 469 htmlFontTagCombined = '' 470 for fmtTag, htmlTag in HTMLFONTTAG: 471 paramValue = params.Get(fmtTag, None) 472 if paramValue: 473 if fmtTag == 'color': 474 paramValue = self.GetColorSyntax(paramValue, html) 475 else: 476 paramValue = FormatValue(paramValue, html) 477 if htmlFontTagCombined: 478 htmlFontTagCombined += ' ' 479 htmlFontTagCombined += '%s=%s' % (htmlTag, paramValue) 480 481 if htmlFontTagCombined: 482 htmlFontTagCombined = '<font %s>' % htmlFontTagCombined 483 currentStack = tagStacks['fontCombined'] 484 if currentStack[-1] != htmlFontTagCombined: 485 if not defaultAdded: 486 retString = defaultHtmlFontTagCombined + retString + '</font>' 487 defaultAdded = True 488 if len(currentStack) > 1: 489 retString += '</font>' 490 currentStack.pop() 491 currentStack.append(htmlFontTagCombined) 492 retString += htmlFontTagCombined 493 for tag, paramName in BOOLTAGS: 494 if params.url and paramName != 'loc': 495 continue 496 tagStack = tagStacks[tag] 497 paramValue = params.get(paramName, False) 498 if paramValue != tagStack[-1] and paramValue: 499 retString += '<%s>' % FormatTag('%s' % tag, html) 500 tagStack.append(True) 501 502 for paramName, defaultAttrName in VALUETAGS: 503 if html and paramName in ('font', 'color', 'fontsize'): 504 continue 505 if paramName == 'color' and tagStacks['url'][-1]: 506 continue 507 currentStack = tagStacks[paramName] 508 currentStackValue = currentStack[-1] 509 currentParamValue = params.get(paramName, None) 510 if currentParamValue != currentStackValue: 511 if currentParamValue: 512 if paramName == 'color': 513 formattedValue = self.GetColorSyntax(currentParamValue, html) 514 else: 515 formattedValue = FormatValue(currentParamValue, html) 516 retString += '<%s=%s>' % (FormatTag(paramName, html), formattedValue) 517 currentStack.append(currentParamValue) 518 519 retString += self.Decode(char) 520 521 if glyphString is not self.sr.paragraphs[-1]: 522 addBreak += 1 523 524 for paramName, defaultAttrName in VALUETAGS: 525 tagStack = tagStacks[paramName] 526 while len(tagStack) > 1: 527 retString += '<%s>' % FormatTag('/%s' % paramName, html) 528 tagStack.pop() 529 530 for tag, paramName in BOOLTAGS: 531 tagStack = tagStacks[tag] 532 while len(tagStack) > 1: 533 retString += '<%s>' % FormatTag('/%s' % tag, html) 534 tagStack.pop() 535 536 if html: 537 currentStack = tagStacks['fontCombined'] 538 while len(currentStack) > 1: 539 currentStack.pop() 540 retString += '</font>' 541 542 if not StripTags(retString): 543 return '' 544 return retString 545 546 def SetDefaultFontSize(self, size): 547 size = check_convert_font_size(size) 548 for glyphString in self.sr.paragraphs: 549 for charData in glyphString: 550 advance, align, color, sbit, char, asc, des, params = charData 551 if params.fontsize == self.defaultFontSize: 552 params.fontsize = size 553 554 self.ReloadGlyphString(glyphString) 555 self.UpdateGlyphString(glyphString) 556 557 self.SetCursorPos(self.globalCursorPos) 558 if self._activeParams: 559 self._activeParams.fontsize = size 560 self.defaultFontSize = size 561 self.UpdatePosition() 562 563 def SetMaxLength(self, value): 564 self.maxletters = value 565 566 def OnSetFocus(self, *args, **kw): 567 super(EditPlainTextCore, self).OnSetFocus(*args, **kw) 568 if not self.readonly and uicore.imeHandler: 569 uicore.imeHandler.SetFocus(self) 570 self.RefreshCursorAndSelection() 571 if hasattr(self, 'RegisterFocus'): 572 self.RegisterFocus(self) 573 574 def OnKillFocus(self, *args, **kw): 575 super(EditPlainTextCore, self).OnKillFocus(*args, **kw) 576 if not self.readonly and uicore.imeHandler: 577 uicore.imeHandler.KillFocus(self) 578 uthread.new(self.RefreshCursorAndSelection) 579 uthread.new(self.OnFocusLost, self) 580 581 def OnFocusLost(self, *args): 582 pass 583 584 def UpdateAttributePanel(self): 585 params = self._activeParams 586 if self.sr.attribPanel is not None: 587 if type(params.color) is types.IntType: 588 c = trinity.TriColor() 589 c.FromInt(params.color) 590 color = (c.r, c.g, c.b) 591 else: 592 color = params.color 593 self.sr.attribPanel.AttribStateChange(params.bold, params.italic, params.underline, params.fontsize, color, params.url) 594 595 @threadutils.throttled(0.5) 596 def UpdateCharacterCounter_throttled(self): 597 self.UpdateCharacterCounter() 598 599 @telemetry.ZONE_METHOD 600 def UpdateCharacterCounter(self): 601 if self.sr.attribPanel is not None: 602 self.sr.attribPanel.UpdateCounter(self._currentLen) 603 if self.customCharCounter is not None: 604 if self._currentLen is not None: 605 currentLen = self._currentLen 606 elif self.countWithTags: 607 currentLen = len(self.GetValue()) 608 self._currentLen = currentLen 609 elif isinstance(self, EditPlainTextCore): 610 currentLen = len(self.GetAllText()) 611 else: 612 currentLen = len(StripTags(self.GetSelectedText(getAll=True))) 613 self.customCharCounter.SetText('%s/%s' % (currentLen, self.counterMax)) 614 615 def GetMenuDelegate(self, node = None): 616 m = [] 617 m.append((MenuLabel('/Carbon/UI/Controls/Common/CopyAll'), self.CopyAll)) 618 if self.HasSelection(): 619 m.append((MenuLabel('/Carbon/UI/Controls/Common/CopySelected'), self.Copy)) 620 m.append((MenuLabel('/Carbon/UI/Controls/Common/CopySelectedWithFormatting'), self.CopyWithFormatting)) 621 if not self.readonly: 622 m.append((MenuLabel('/Carbon/UI/Controls/Common/CutSelected'), self.Cut)) 623 clipboard = GetClipboardData() 624 if clipboard and not self.readonly: 625 m.append((MenuLabel('/Carbon/UI/Controls/Common/Paste'), self.Paste, (clipboard,))) 626 return m 627 628 def SelectLineUnderCursor(self): 629 node = self.GetActiveNode() 630 fromIdx, toIdx = node.startCursorIndex, node.endCursorIndex 631 self.SetSelectionRange(fromIdx, toIdx, updateCursor=False) 632 self.SetSelectionInitPos(fromIdx) 633 self.SetCursorPos(toIdx) 634 635 def FindWordBoundariesFromGlobalCursor(self, wordShift = 0): 636 text = self.GetAllText(newLineStr=' ') 637 boundaries = [] 638 counter = 0 639 for each in uiconst.WORD_BOUNDARY_REGEX.split(text): 640 if each: 641 counter += len(each) 642 boundaries.append(counter) 643 644 wordStart = 0 645 wordEnd = None 646 for boundary in boundaries: 647 if boundary >= self.globalCursorPos + wordShift: 648 wordEnd = boundary 649 break 650 wordStart = boundary 651 652 if wordEnd is None: 653 wordEnd = len(text) 654 leftOffset = wordStart - self.globalCursorPos 655 rightOffset = wordEnd - self.globalCursorPos 656 return (leftOffset, rightOffset, text) 657 658 def SelectWordUnderCursor(self): 659 leftBound, rightBound, allText = self.FindWordBoundariesFromGlobalCursor() 660 fromIdx, toIdx = self.globalCursorPos + leftBound, self.globalCursorPos + rightBound 661 while toIdx > fromIdx and allText[toIdx - 1] == ' ': 662 toIdx -= 1 663 664 self.SetSelectionRange(fromIdx, toIdx, updateCursor=False) 665 self.SetSelectionInitPos(fromIdx) 666 self.SetCursorPos(toIdx) 667 668 def SetSelectionInitPos(self, globalIndex): 669 self.globalSelectionInitpos = globalIndex 670 671 @telemetry.ZONE_METHOD 672 def SetSelectionRange(self, fromCharIndex, toCharIndex, updateCursor = True): 673 if fromCharIndex == toCharIndex: 674 fromCharIndex, toCharIndex = (None, None) 675 globalSelectionRange = [fromCharIndex, toCharIndex] 676 globalSelectionRange.sort() 677 self.globalSelectionRange = globalSelectionRange 678 if fromCharIndex is None or toCharIndex is None: 679 self.SetSelectionInitPos(self.globalCursorPos) 680 if updateCursor: 681 self.RefreshCursorAndSelection() 682 683 @telemetry.ZONE_METHOD 684 def SetCursorPos(self, globalIndex, updateActiveParams = True): 685 if globalIndex == -1: 686 globalIndex = self._maxGlobalCursorIndex 687 maxIndex = 0 688 for gs in self.sr.paragraphs: 689 maxIndex += len(gs) + 1 690 691 maxIndex -= 1 692 self._maxGlobalCursorIndex = max(0, maxIndex) 693 self.globalCursorPos = max(0, min(self._maxGlobalCursorIndex, globalIndex)) 694 self.RefreshCursorAndSelection(updateActiveParams=updateActiveParams) 695 696 @telemetry.ZONE_METHOD 697 def RefreshCursorAndSelection(self, updateActiveParams = True): 698 if self.destroyed: 699 return 700 i = 0 701 stackShift = 0 702 lastGlyphString = None 703 globalCursorIndex = 0 704 stackCursor = 0 705 fromIdx, toIdx = self.globalSelectionRange 706 for node in self.sr.nodes: 707 if not issubclass(node.decoClass, SE_EditTextlineCore): 708 continue 709 if node._endIndex is None: 710 letterCountInLine = 0 711 else: 712 letterCountInLine = node._endIndex - node._startIndex 713 node.letterCountInLine = letterCountInLine 714 if lastGlyphString is not None and lastGlyphString is not node.glyphString: 715 stackShift += 1 716 stackCursor = 0 717 node.startCursorIndex = globalCursorIndex + stackShift 718 node.endCursorIndex = node.startCursorIndex + letterCountInLine 719 node.stackCursorIndex = stackCursor 720 globalCursorIndex += letterCountInLine 721 stackCursor += letterCountInLine 722 lastGlyphString = node.glyphString 723 if updateActiveParams and node.startCursorIndex <= self.globalCursorPos <= node.endCursorIndex: 724 self._activeNode = node 725 stackCursorIndex = self.globalCursorPos - node.startCursorIndex + node.stackCursorIndex 726 self._activeParams = self.GetPriorParams(node.glyphString, stackCursorIndex) or self.GetFontParams() 727 self._activeParams = self._activeParams.Copy() 728 self.UpdateAttributePanel() 729 if not self.readonly: 730 if node.globalCursorPos is not None: 731 node.globalCursorPos = None 732 if node.panel: 733 node.panel.UpdateCursor() 734 if fromIdx is None or not (fromIdx <= node.startCursorIndex <= toIdx or node.startCursorIndex <= fromIdx <= node.endCursorIndex): 735 node.selectionStartIndex = None 736 node.selectionEndIndex = None 737 else: 738 node.selectionStartIndex = max(0, fromIdx) 739 node.selectionEndIndex = max(0, toIdx) 740 if node.panel and hasattr(node.panel, 'UpdateSelectionHilite'): 741 node.panel.UpdateSelectionHilite() 742 i += 1 743 744 if not self.readonly: 745 node = self.GetActiveNode() 746 if node: 747 node.globalCursorPos = self.globalCursorPos 748 if node.panel: 749 node.panel.UpdateCursor() 750 751 @telemetry.ZONE_METHOD 752 def GetActiveNode(self): 753 ret = getattr(self, '_activeNode', None) 754 if ret is None: 755 ret = self.sr.nodes[0] 756 return ret 757 758 @telemetry.ZONE_METHOD 759 def GetSelectedText(self): 760 ret = '' 761 selectedData = self.GetSeletedCharData() 762 if not selectedData: 763 return ret 764 for glyphString, gsData in selectedData: 765 for charIdx, (advance, align, color, sbit, char, asc, des, params) in gsData: 766 ret += char 767 768 ret += '\r\n' 769 770 return ret[:-2] 771 772 @telemetry.ZONE_METHOD 773 def GetAllText(self, newLineStr = '\r\n'): 774 ret = '' 775 for glyphString in self.sr.paragraphs: 776 ret += glyphString.GetText() 777 ret += newLineStr 778 779 return ret[:-len(newLineStr)] 780 781 def OnMouseUpDelegate(self, _node, *args): 782 self._selecting = 0 783 self.sr.scrollTimer = None 784 self.SetCursorPos(self.globalCursorPos) 785 786 def OnMouseDownDelegate(self, _node, *args): 787 self._selecting = 1 788 if _node and _node.panel: 789 self.SetCursorFromNodeAndMousePos(_node) 790 if uicore.uilib.Key(uiconst.VK_SHIFT) and self.globalSelectionInitpos is not None: 791 fromIdx, toIdx = self.globalSelectionRange 792 newIndex = self.globalCursorPos 793 toIndex = self.globalSelectionInitpos 794 if newIndex > toIndex: 795 self.SetSelectionRange(toIndex, newIndex) 796 else: 797 self.SetSelectionRange(newIndex, toIndex) 798 else: 799 self.SetSelectionRange(None, None) 800 self.SetSelectionInitPos(self.globalCursorPos) 801 self.sr.scrollTimer = AutoTimer(100, self.ScrollTimer) 802 803 def SetCursorFromNodeAndMousePos(self, node): 804 if node.panel is not None: 805 internalPos = node.panel.GetInternalCursorPos() 806 self.SetCursorPos(node.startCursorIndex + internalPos) 807 else: 808 self.SetCursorPos(-1) 809 810 def GetLastTextline(self): 811 totalLines = len(self.sr.content.children) 812 for i in xrange(totalLines): 813 nodePanel = self.sr.content.children[totalLines - i - 1] 814 if isinstance(nodePanel, SE_EditTextlineCore): 815 return nodePanel 816 817 def CrawlForTextline(self, mo): 818 if isinstance(mo, SE_EditTextlineCore): 819 return mo 820 if mo.parent: 821 if mo.parent is uicore.desktop: 822 return None 823 return self.CrawlForTextline(mo.parent) 824 825 def ScrollTimer(self): 826 if not self._selecting or self.globalSelectionInitpos is None: 827 self.sr.scrollTimer = None 828 return 829 if not uicore.uilib.leftbtn: 830 self.sr.scrollTimer = None 831 self._selecting = 0 832 return 833 toAffect = None 834 if uicore.uilib.mouseOver.IsUnder(self): 835 toAffect = self.CrawlForTextline(uicore.uilib.mouseOver) 836 if toAffect is None: 837 toAffect = self.GetLineAtCursorLevel() 838 aL, aT, aW, aH = self.GetAbsolute() 839 if uicore.uilib.y < aT: 840 self.Scroll(1) 841 elif uicore.uilib.y > aT + aH: 842 self.Scroll(-1) 843 if toAffect is None: 844 return 845 node = toAffect.sr.node 846 if node is None: 847 return 848 self.SetCursorFromNodeAndMousePos(node) 849 if self.globalCursorPos > self.globalSelectionInitpos: 850 self.SetSelectionRange(self.globalSelectionInitpos, self.globalCursorPos) 851 else: 852 self.SetSelectionRange(self.globalCursorPos, self.globalSelectionInitpos) 853 854 def GetLineAtCursorLevel(self): 855 aL, aT, aW, aH = self.GetAbsolute() 856 if uicore.uilib.y < aT: 857 return self.sr.content.children[0] 858 if uicore.uilib.y > aT + aH: 859 return self.sr.content.children[-1] 860 each = None 861 for each in self.sr.content.children: 862 l, t, w, h = each.GetAbsolute() 863 if t < uicore.uilib.y <= t + h: 864 return each 865 866 return each 867 868 def OnDragEnterDelegate(self, node, nodes): 869 if self.readonly: 870 return 871 self.SetCursorFromNodeAndMousePos(node) 872 873 def OnDragMoveDelegate(self, node, nodes): 874 if self.readonly: 875 return 876 self.SetCursorFromNodeAndMousePos(node) 877 878 def OnDropDataDelegate(self, node, nodes): 879 pass 880 881 def OnContentDropData(self, dragObj, nodes): 882 self._ResetCurrentLen() 883 self.SetCursorPos(self._maxGlobalCursorIndex) 884 node = self.GetActiveNode() 885 self.OnDropDataDelegate(node, nodes) 886 self.UpdateCharacterCounter() 887 888 def OnDropData(self, dragObj, nodes): 889 return self.OnContentDropData(dragObj, nodes) 890 891 def OnMouseUp(self, *args): 892 self._selecting = 0 893 self.sr.scrollTimer = None 894 895 def OnMouseDown(self, button, *args): 896 if button != uiconst.MOUSELEFT: 897 return 898 if len(self.sr.content.children): 899 shift = uicore.uilib.Key(uiconst.VK_SHIFT) 900 lastEntry = self.sr.content.children[-1] 901 l, t, w, h = lastEntry.GetAbsolute() 902 if uicore.uilib.y > t + h: 903 if shift: 904 selectionStartIndex, selectionEndIndex = self.globalSelectionRange 905 self.SetSelectionRange(selectionStartIndex or self.globalCursorPos, self._maxGlobalCursorIndex, updateCursor=False) 906 else: 907 self.SetSelectionRange(None, None, updateCursor=False) 908 self.SetSelectionInitPos(self._maxGlobalCursorIndex) 909 self.SetCursorPos(self._maxGlobalCursorIndex) 910 self._selecting = 1 911 912 @telemetry.ZONE_METHOD 913 def OnKeyDown(self, vkey, flag, *args, **kw): 914 isMac = sys.platform.startswith('darwin') 915 916 def k(win, mac): 917 if isMac: 918 return mac 919 return win 920 921 ctrl = uicore.uilib.Key(uiconst.VK_CONTROL) 922 alt = uicore.uilib.Key(uiconst.VK_MENU) 923 cmd = uicore.uilib.Key(uiconst.VK_LWIN) 924 if vkey in (uiconst.VK_LEFT, 925 uiconst.VK_RIGHT, 926 uiconst.VK_UP, 927 uiconst.VK_DOWN, 928 uiconst.VK_HOME, 929 uiconst.VK_END): 930 if self.globalCursorPos is None: 931 return 932 preFromIdx, preToIdx = self.globalSelectionRange 933 shift = uicore.uilib.Key(uiconst.VK_SHIFT) 934 if not shift: 935 self.SetSelectionRange(None, None) 936 else: 937 preFromIdx, preToIdx = (None, None) 938 node = self.GetActiveNode() 939 newCursorPos = self.globalCursorPos 940 if vkey == uiconst.VK_LEFT and (not isMac or not cmd): 941 if k(win=ctrl, mac=alt): 942 leftBound, rightBound, allText = self.FindWordBoundariesFromGlobalCursor() 943 newCursorPos = self.globalCursorPos + leftBound 944 else: 945 fromIdx, toIdx = self.globalSelectionRange 946 if preFromIdx is not None and preToIdx is not None: 947 newCursorPos = min(preFromIdx, preToIdx) 948 else: 949 newCursorPos = max(0, self.globalCursorPos - 1) 950 if shift: 951 selectionStartIndex, selectionEndIndex = self.globalSelectionRange 952 if selectionStartIndex is None: 953 self.SetSelectionRange(newCursorPos, self.globalCursorPos, updateCursor=False) 954 elif self.globalCursorPos == selectionStartIndex: 955 self.SetSelectionRange(newCursorPos, selectionEndIndex, updateCursor=False) 956 else: 957 self.SetSelectionRange(selectionStartIndex, newCursorPos, updateCursor=False) 958 elif vkey == uiconst.VK_RIGHT and (not isMac or not cmd): 959 if k(win=ctrl, mac=alt): 960 leftBound, rightBound, allText = self.FindWordBoundariesFromGlobalCursor(wordShift=1) 961 newCursorPos = self.globalCursorPos + rightBound 962 elif preFromIdx is not None and preToIdx is not None: 963 newCursorPos = max(preFromIdx, preToIdx) 964 else: 965 newCursorPos = self.globalCursorPos + 1 966 if shift: 967 selectionStartIndex, selectionEndIndex = self.globalSelectionRange 968 if selectionStartIndex is None: 969 self.SetSelectionRange(self.globalCursorPos, newCursorPos, updateCursor=False) 970 elif self.globalCursorPos == selectionStartIndex: 971 self.SetSelectionRange(newCursorPos, selectionEndIndex, updateCursor=False) 972 else: 973 self.SetSelectionRange(selectionStartIndex, newCursorPos, updateCursor=False) 974 elif vkey == uiconst.VK_UP: 975 if not k(win=ctrl, mac=cmd): 976 self.OnUp() 977 posInLine = self.globalCursorPos - node.startCursorIndex 978 if node.idx > 0: 979 nodeAbove = self.GetNode(node.idx - 1) 980 if nodeAbove and nodeAbove.endCursorIndex is not None: 981 newCursorPos = min(nodeAbove.endCursorIndex, nodeAbove.startCursorIndex + posInLine) 982 else: 983 newCursorPos = 0 984 if shift: 985 selectionStartIndex, selectionEndIndex = self.globalSelectionRange 986 if selectionStartIndex is None: 987 self.SetSelectionRange(newCursorPos, self.globalCursorPos, updateCursor=False) 988 elif self.globalCursorPos == selectionStartIndex: 989 self.SetSelectionRange(newCursorPos, selectionEndIndex, updateCursor=False) 990 else: 991 self.SetSelectionRange(selectionStartIndex, newCursorPos, updateCursor=False) 992 elif vkey == uiconst.VK_DOWN: 993 if not k(win=ctrl, mac=cmd): 994 self.OnDown() 995 posInLine = self.globalCursorPos - node.startCursorIndex 996 nodeBelow = self.GetNode(node.idx + 1) 997 if nodeBelow and nodeBelow.startCursorIndex is not None: 998 newCursorPos = nodeBelow.startCursorIndex + min(posInLine, nodeBelow.letterCountInLine) 999 else: 1000 newCursorPos = self._maxGlobalCursorIndex 1001 if shift: 1002 selectionStartIndex, selectionEndIndex = self.globalSelectionRange 1003 if selectionStartIndex is None: 1004 self.SetSelectionRange(self.globalCursorPos, newCursorPos, updateCursor=False) 1005 elif self.globalCursorPos == selectionStartIndex: 1006 self.SetSelectionRange(newCursorPos, selectionEndIndex, updateCursor=False) 1007 else: 1008 self.SetSelectionRange(selectionStartIndex, newCursorPos, updateCursor=False) 1009 elif vkey == uiconst.VK_HOME or vkey == uiconst.VK_LEFT and isMac and cmd: 1010 self.OnHome() 1011 if vkey == uiconst.VK_HOME and ctrl: 1012 newCursorPos = 0 1013 else: 1014 newCursorPos = node.startCursorIndex 1015 if shift: 1016 selectionStartIndex, selectionEndIndex = self.globalSelectionRange 1017 if selectionStartIndex is None: 1018 self.SetSelectionRange(newCursorPos, self.globalCursorPos, updateCursor=False) 1019 elif self.globalCursorPos == selectionStartIndex: 1020 self.SetSelectionRange(newCursorPos, selectionEndIndex, updateCursor=False) 1021 else: 1022 self.SetSelectionRange(newCursorPos, selectionEndIndex, updateCursor=False) 1023 elif vkey == uiconst.VK_END or vkey == uiconst.VK_RIGHT and isMac and cmd: 1024 self.OnEnd() 1025 if vkey == uiconst.VK_END and ctrl: 1026 newCursorPos = self._maxGlobalCursorIndex 1027 else: 1028 newCursorPos = node.endCursorIndex 1029 if shift: 1030 selectionStartIndex, selectionEndIndex = self.globalSelectionRange 1031 if selectionStartIndex is None: 1032 self.SetSelectionRange(self.globalCursorPos, newCursorPos, updateCursor=False) 1033 elif self.globalCursorPos == selectionStartIndex: 1034 self.SetSelectionRange(selectionStartIndex, newCursorPos, updateCursor=False) 1035 else: 1036 self.SetSelectionRange(selectionStartIndex, newCursorPos, updateCursor=False) 1037 self.SetCursorPos(newCursorPos) 1038 node = self.GetActiveNode() 1039 if node: 1040 self.ShowNodeIdx(node.idx) 1041 if self.readonly: 1042 return 1043 if k(win=ctrl, mac=cmd): 1044 if vkey == uiconst.VK_B: 1045 self.ToggleBold() 1046 if vkey == uiconst.VK_U: 1047 self.ToggleUnderline() 1048 if vkey == uiconst.VK_I: 1049 self.ToggleItalic() 1050 if vkey == uiconst.VK_ADD: 1051 self.EnlargeSize() 1052 if vkey == uiconst.VK_SUBTRACT: 1053 self.ReduceSize() 1054 if vkey == uiconst.VK_UP: 1055 self.CtrlUp(self) 1056 if vkey == uiconst.VK_DOWN: 1057 self.CtrlDown(self) 1058 if vkey in (uiconst.VK_B, 1059 uiconst.VK_U, 1060 uiconst.VK_I, 1061 uiconst.VK_ADD, 1062 uiconst.VK_SUBTRACT): 1063 self.UpdateCharacterCounter() 1064 return 1065 if vkey == uiconst.VK_DELETE: 1066 self.OnChar(127, flag) 1067 1068 def ReadOnly(self, *args): 1069 self.readonly = True 1070 if self.sr.attribPanel is not None: 1071 self.sr.attribPanel.ShowOrHideCharacterCounter(showCounter=False) 1072 1073 def Editable(self, *args): 1074 self.readonly = False 1075 if self.sr.attribPanel is not None: 1076 self.sr.attribPanel.ShowOrHideCharacterCounter(showCounter=True) 1077 1078 @telemetry.ZONE_METHOD 1079 def OnChar(self, char, flag): 1080 if self.readonly: 1081 return False 1082 if char < 32 and char not in (uiconst.VK_RETURN, uiconst.VK_BACK): 1083 return False 1084 if self.globalCursorPos is None: 1085 return False 1086 self.keybuffer.append(char) 1087 if not self.updating: 1088 if char == uiconst.VK_RETURN and not uicore.uilib.Key(uiconst.VK_SHIFT) and self.OnReturn: 1089 self.keybuffer.pop(-1) 1090 return uthread.new(self.OnReturn) 1091 try: 1092 self.updating = 1 1093 while len(self.keybuffer): 1094 char = self.keybuffer.pop(0) 1095 if self.DeleteSelected() and char in [127, uiconst.VK_BACK]: 1096 continue 1097 self.Insert(char) 1098 1099 if char == uiconst.VK_BACK: 1100 self.UpdateCharacterCounter_throttled() 1101 else: 1102 self.UpdateCharacterCounter() 1103 finally: 1104 self.updating = 0 1105 1106 return True 1107 1108 def SelectAll(self, *args): 1109 self.SetSelectionRange(0, self._maxGlobalCursorIndex) 1110 1111 def Copy(self): 1112 selstring = self.GetSelectedText() 1113 if selstring: 1114 blue.pyos.SetClipboardData(selstring) 1115 1116 def CopyAll(self): 1117 all = self.GetAllText() 1118 if all: 1119 blue.pyos.SetClipboardData(all) 1120 1121 def CopyWithFormatting(self): 1122 selectedData = self.GetSeletedCharData() 1123 if not selectedData: 1124 return '' 1125 paragraphs = [] 1126 for glyphString, gsData in selectedData: 1127 line = [] 1128 for charIdx, charData in gsData: 1129 line.append(charData) 1130 1131 paragraphs.append(line) 1132 1133 text = self.GetValueForParagraphs(paragraphs=paragraphs) 1134 if text: 1135 blue.pyos.SetClipboardData(text) 1136 1137 def _CopyAllWithFormatting(self): 1138 all = self.GetValue(html=True) 1139 if all: 1140 blue.pyos.SetClipboardData(all) 1141 1142 def Cut(self): 1143 self.Copy() 1144 if not self.readonly: 1145 self.DeleteSelected() 1146 1147 def IsEnoughRoomForInsert(self, textLen): 1148 if not self.maxletters: 1149 return True 1150 if self._currentLen is not None: 1151 currentLen = self._currentLen 1152 else: 1153 currentLen = self.GetLenghtOfCurrentText() 1154 if currentLen + textLen <= self.maxletters: 1155 return True 1156 return textLen <= self.RoomLeft() 1157 1158 def IsEnoughRoomForSet(self, text): 1159 if not self.maxletters: 1160 return True 1161 if self.countWithTags: 1162 textLen = len(text) 1163 else: 1164 textLen = len(StripTags(text)) 1165 if textLen <= self.maxletters: 1166 return True 1167 return textLen <= self.RoomLeft() 1168 1169 def RoomLeft(self): 1170 if not self.maxletters: 1171 return None 1172 currentLen = self.GetLenghtOfCurrentText() 1173 return max(0, self.maxletters + len(self.GetSelectedText()) - currentLen) 1174 1175 def GetLenghtOfCurrentText(self): 1176 if self.countWithTags: 1177 if self._currentLen is not None: 1178 return self._currentLen 1179 currentLen = len(self.GetValue()) 1180 return currentLen 1181 else: 1182 return len(self.GetAllText()) 1183 1184 def Paste(self, text): 1185 self._ResetCurrentLen() 1186 if self.ValidatePaste: 1187 text = self.ValidatePaste(text) 1188 roomLeft = self.RoomLeft() 1189 if roomLeft is not None and roomLeft < len(text): 1190 prompt_player_fsd_dialog('uiwarning03') 1191 text = text[:roomLeft] 1192 if self.readonly or not text: 1193 return 1194 self.DeleteSelected() 1195 self.InsertText(text) 1196 node = self.GetActiveNode() 1197 if node: 1198 self.ShowNodeIdx(node.idx) 1199 self.UpdatePosition() 1200 uicore.registry.SetFocus(self) 1201 self.UpdateCharacterCounter() 1202 self.OnChange() 1203 1204 def ValidatePaste(self, text): 1205 return text 1206 1207 def GetFontParams(self): 1208 std = uicore.font.GetParams() 1209 if self._activeParams: 1210 std.color = self._activeParams.color 1211 std.fontsize = self._activeParams.fontsize 1212 else: 1213 std.color = self.defaultFontColor 1214 std.fontsize = self.defaultFontSize 1215 for tag, paramName in BOOLTAGS: 1216 tagStack = getattr(self, 'tagStack_%s' % tag, []) 1217 std.Set(paramName, bool(tagStack)) 1218 1219 for paramName, defaultAttrName in VALUETAGS: 1220 tagStack = getattr(self, 'tagStack_%s' % paramName, []) 1221 if tagStack: 1222 std.Set(paramName, tagStack[-1]) 1223 1224 std.fontStyle = self.defaultFontStyle 1225 std.fontFamily = self.defaultFontFamily 1226 std.fontPath = self.defaultFontPath 1227 return std 1228 1229 @telemetry.ZONE_METHOD 1230 def CheckLineWrap(self, glyphString): 1231 text = unicode(glyphString.GetText()) 1232 if not text: 1233 return [(0, 0, 0, 12, 12)] 1234 maxWidth = self.GetContentWidth() - TEXTSIDEMARGIN - 1 1235 if maxWidth < 0: 1236 return [(0, 0, 0, 12, 12)] 1237 wpl = eveLocalization.WrapPointList(text, session.languageID) 1238 wrapPoints = wpl.GetLinebreakPoints() 1239 wrappedLines = [] 1240 lineWidth = 0 1241 lineHeight = 0 1242 lineBaseLine = 0 1243 lineStartWrapPoint = 0 1244 lastWrapPoint = 0 1245 textLength = len(text) 1246 maxWidth = self.ScaleDpi(maxWidth) 1247 for wrapPoint in wrapPoints + [textLength]: 1248 if lastWrapPoint == wrapPoint: 1249 continue 1250 wordGlyphs = glyphString[lastWrapPoint:wrapPoint] 1251 sizeData = zip(*[ (glyph[0], glyph[5] - glyph[6], glyph[5]) for glyph in wordGlyphs ]) 1252 wordWidth = sum(sizeData[0]) 1253 if wordWidth > maxWidth: 1254 if lineWidth: 1255 wrappedLines.append((lineStartWrapPoint, 1256 lastWrapPoint, 1257 lineWidth, 1258 lineBaseLine, 1259 lineHeight)) 1260 lineStartWrapPoint = lastWrapPoint 1261 lineWidth = 0 1262 lineHeight = 0 1263 lineBaseLine = 0 1264 glyphCounter = 0 1265 for glyph in wordGlyphs: 1266 glyphWidth = glyph[0] 1267 glyphBaseLine = glyph[5] 1268 glyphHeight = glyphBaseLine - glyph[6] 1269 if lineWidth + glyphWidth > maxWidth: 1270 wrappedLines.append((lineStartWrapPoint, 1271 lineStartWrapPoint + glyphCounter, 1272 lineWidth, 1273 lineBaseLine, 1274 lineHeight)) 1275 lineWidth = glyphWidth 1276 lineHeight = glyphHeight 1277 lineBaseLine = glyphBaseLine 1278 lineStartWrapPoint = lineStartWrapPoint + glyphCounter 1279 glyphCounter = 1 1280 else: 1281 lineWidth += glyphWidth 1282 lineHeight = max(lineHeight, glyphHeight) 1283 lineBaseLine = max(lineBaseLine, glyphBaseLine) 1284 glyphCounter += 1 1285 1286 elif lineWidth + wordWidth > maxWidth: 1287 wrappedLines.append((lineStartWrapPoint, 1288 lastWrapPoint, 1289 lineWidth, 1290 lineBaseLine, 1291 lineHeight)) 1292 lineWidth = wordWidth 1293 lineHeight = max(sizeData[1]) 1294 lineBaseLine = max(sizeData[2]) 1295 lineStartWrapPoint = lastWrapPoint 1296 else: 1297 lineWidth += wordWidth 1298 lineHeight = max(lineHeight, max(sizeData[1])) 1299 lineBaseLine = max(lineBaseLine, max(sizeData[2])) 1300 lastWrapPoint = wrapPoint 1301 1302 if lineStartWrapPoint < textLength: 1303 wrappedLines.append((lineStartWrapPoint, 1304 textLength, 1305 lineWidth, 1306 lineBaseLine, 1307 lineHeight)) 1308 return wrappedLines 1309 1310 @telemetry.ZONE_METHOD 1311 def GetNodesWithGlyphString(self, glyphString): 1312 ret = [] 1313 glyphStringID = id(glyphString) 1314 for node in self.sr.nodes: 1315 if id(node.glyphString) == glyphStringID: 1316 ret.append(node) 1317 elif ret: 1318 break 1319 1320 return ret 1321 1322 @telemetry.ZONE_METHOD 1323 def UpdateGlyphString(self, glyphString, advance = None, stackCursorIndex = None, nodesByGlyphstringIDs = None): 1324 if nodesByGlyphstringIDs: 1325 glyphStringID = id(glyphString) 1326 allNodesUsingGlyphString = self.GetNodesByGlyphStringID(glyphStringID, nodesByGlyphstringIDs) 1327 else: 1328 allNodesUsingGlyphString = self.GetNodesWithGlyphString(glyphString) 1329 lineIndexes = self.CheckLineWrap(glyphString) 1330 if stackCursorIndex is not None: 1331 lenNew = len(lineIndexes) 1332 lenOld = len(allNodesUsingGlyphString) 1333 if lenNew < lenOld: 1334 rem = allNodesUsingGlyphString[-(lenOld - lenNew):] 1335 for each in rem: 1336 allNodesUsingGlyphString.remove(each) 1337 1338 self.RemoveNodesRaw(rem, nodesByGlyphstringIDs) 1339 elif lenNew > lenOld: 1340 newNodes = [] 1341 if allNodesUsingGlyphString: 1342 startAt = allNodesUsingGlyphString[-1].idx + 1 1343 else: 1344 startAt = 0 1345 for x in xrange(lenNew - lenOld): 1346 newNode = self.CreateNode() 1347 newNode.glyphString = glyphString 1348 newNode._startIndex = 0 1349 newNode._endIndex = 0 1350 newNode._width = 0 1351 newNode._baseHeight = self.ScaleDpi(12) 1352 newNode._baseLine = 12 1353 newNodes.append(newNode) 1354 1355 self.InsertNodesRaw(startAt, newNodes, nodesByGlyphstringIDs) 1356 allNodesUsingGlyphString += newNodes 1357 updateCount = 0 1358 for i, lineData in enumerate(lineIndexes): 1359 old = allNodesUsingGlyphString[i] 1360 startIdx, endIdx, width, baseLine, baseHeight = lineData 1361 addAdv = 0 1362 if stackCursorIndex <= endIdx: 1363 addAdv = advance 1364 if old._startIndex + addAdv != startIdx or old._endIndex + addAdv != endIdx: 1365 doUpdate = True 1366 else: 1367 doUpdate = False 1368 old._startIndex = startIdx 1369 old._endIndex = endIdx 1370 old._width = width 1371 old._baseHeight = baseHeight 1372 old._baseLine = baseLine 1373 if doUpdate: 1374 if old.panel: 1375 old.panel.Load(old) 1376 updateCount += 1 1377 1378 else: 1379 if allNodesUsingGlyphString: 1380 insertAt = allNodesUsingGlyphString[0].idx 1381 self.RemoveNodesRaw(allNodesUsingGlyphString, nodesByGlyphstringIDs) 1382 else: 1383 insertAt = 0 1384 newNodes = [] 1385 for startIdx, endIdx, width, baseLine, baseHeight in lineIndexes: 1386 newNode = self.CreateNode() 1387 newNode.glyphString = glyphString 1388 newNode._startIndex = startIdx 1389 newNode._endIndex = endIdx 1390 newNode._width = width 1391 newNode._baseHeight = baseHeight 1392 newNode._baseLine = baseLine 1393 newNodes.append(newNode) 1394 1395 self.InsertNodesRaw(insertAt, newNodes, nodesByGlyphstringIDs) 1396 allNodesUsingGlyphString = newNodes 1397 self.RefreshNodeIndexes() 1398 return allNodesUsingGlyphString 1399 1400 @telemetry.ZONE_METHOD 1401 def RemoveNodesRaw(self, nodes, nodesByGlyphstringIDs = None): 1402 for node in nodes: 1403 if node.panel: 1404 node.panel.Close() 1405 if node in self.sr.nodes: 1406 self.sr.nodes.remove(node) 1407 1408 self.RemoveNodesFromIDDict(nodesByGlyphstringIDs, nodes) 1409 1410 @telemetry.ZONE_METHOD 1411 def InsertNodesRaw(self, fromIdx, nodesData, nodesByGlyphstringIDs = None): 1412 if fromIdx == -1: 1413 fromIdx = len(self.sr.nodes) 1414 idx = fromIdx 1415 for data in nodesData: 1416 newnode = self.AddNode(idx, data) 1417 idx += 1 1418 1419 self.AddNodesToIDDict(nodesByGlyphstringIDs, nodesData) 1420 1421 def UpdateContentSize(self, clipperWidth, clipperHeight, contentHeight): 1422 self.sr.content.width = clipperWidth 1423 oldHeight = self.sr.content.height 1424 if self._autoSize: 1425 self.sr.content.height = contentHeight 1426 self.height = contentHeight + self.sr.maincontainer.padTop + self.sr.maincontainer.padBottom + self.sr.clipper.padTop + self.sr.clipper.padBottom 1427 else: 1428 self.sr.content.height = max(clipperHeight, contentHeight) 1429 if oldHeight != contentHeight: 1430 if hasattr(self, 'OnContentSizeChanged'): 1431 self.OnContentSizeChanged(clipperWidth, self.sr.content.height) 1432 1433 @telemetry.ZONE_METHOD 1434 def InsertNewGlyphString(self, glyphString, glyphStringIndex, nodeIndex, nodesByGlyphStringIDs = None): 1435 self.sr.paragraphs.insert(glyphStringIndex, glyphString) 1436 lineIndexes = self.CheckLineWrap(glyphString) 1437 newNodes = [] 1438 for startIdx, endIdx, width, baseLine, baseHeight in lineIndexes: 1439 newNode = self.CreateNode() 1440 newNode.glyphString = glyphString 1441 newNode._startIndex = startIdx 1442 newNode._endIndex = endIdx 1443 newNode._width = width 1444 newNode._baseHeight = baseHeight 1445 newNode._baseLine = baseLine 1446 newNodes.append(newNode) 1447 1448 self.InsertNodesRaw(nodeIndex, newNodes, nodesByGlyphStringIDs) 1449 1450 def AddNodesToIDDict(self, nodesByGlyphstringIDs, newNodes): 1451 if nodesByGlyphstringIDs is None: 1452 return 1453 for eachNode in newNodes: 1454 glyphStringID = id(eachNode.glyphString) 1455 if eachNode not in nodesByGlyphstringIDs[glyphStringID]: 1456 nodesByGlyphstringIDs[glyphStringID].append(eachNode) 1457 1458 def RemoveNodesFromIDDict(self, nodesByGlyphstringIDs, nodesToRemove): 1459 if not nodesByGlyphstringIDs: 1460 return 1461 for eachNode in nodesToRemove: 1462 glyphStringID = id(eachNode.glyphString) 1463 if glyphStringID in nodesByGlyphstringIDs and eachNode in nodesByGlyphstringIDs[glyphStringID]: 1464 nodesByGlyphstringIDs[glyphStringID].remove(eachNode) 1465 1466 @telemetry.ZONE_METHOD 1467 def RemoveGlyphString(self, glyphString): 1468 allNodesInStack = self.GetNodesWithGlyphString(glyphString) 1469 if allNodesInStack: 1470 self.RemoveNodesRaw(allNodesInStack) 1471 gsIdx = self.GetGlyphStringIndex(glyphString) 1472 if gsIdx is not None: 1473 del self.sr.paragraphs[gsIdx] 1474 1475 @telemetry.ZONE_METHOD 1476 def GetGlyphStringIndex(self, glyphString): 1477 for glyphStringIndex, _glyphString in enumerate(self.sr.paragraphs): 1478 if _glyphString is glyphString: 1479 return glyphStringIndex 1480 1481 @telemetry.ZONE_METHOD 1482 def Insert(self, char): 1483 if char not in (uiconst.VK_BACK, 127) and not self.IsEnoughRoomForInsert(1): 1484 prompt_player_fsd_dialog('uiwarning03') 1485 return 1486 node = self.GetActiveNode() 1487 if node is None: 1488 return 1489 stackCursorIndex = self.globalCursorPos - node.startCursorIndex + node.stackCursorIndex 1490 glyphString = node.glyphString 1491 glyphStringIndex = self.GetGlyphStringIndex(glyphString) 1492 cursorAdvance = 0 1493 setParams = None 1494 if char == uiconst.VK_RETURN: 1495 self._ResetCurrentLen() 1496 newGS = uicore.font.GetGlyphString() 1497 newGS += glyphString[stackCursorIndex:] 1498 glyphString.FlushFromIndex(stackCursorIndex) 1499 gsNodes = self.UpdateGlyphString(glyphString) 1500 gsIndex = self.GetGlyphStringIndex(glyphString) 1501 self.InsertNewGlyphString(newGS, gsIndex + 1, gsNodes[-1].idx + 1) 1502 self.RefreshNodes() 1503 cursorAdvance = 1 1504 elif char == 127: 1505 self._ResetCurrentLen() 1506 if stackCursorIndex == len(glyphString): 1507 if self.sr.paragraphs[-1] is not glyphString: 1508 idx = self.GetGlyphStringIndex(glyphString) 1509 glyphStringBelow = self.sr.paragraphs[idx + 1] 1510 glyphString += glyphStringBelow 1511 self.UpdateGlyphString(glyphString) 1512 self.RemoveGlyphString(glyphStringBelow) 1513 else: 1514 glyphString.Remove(stackCursorIndex, stackCursorIndex + 1) 1515 self.UpdateGlyphString(glyphString, -1, stackCursorIndex + 1) 1516 elif char == uiconst.VK_BACK: 1517 setParams = self.GetPriorParams(node.glyphString, stackCursorIndex) 1518 if stackCursorIndex > 0: 1519 glyphString.Remove(stackCursorIndex - 1, stackCursorIndex) 1520 self.UpdateGlyphString(glyphString, -1, stackCursorIndex - 1) 1521 cursorAdvance = -1 1522 self._ResetCurrentLen() 1523 elif glyphStringIndex > 0: 1524 glyphStringAbove = self.sr.paragraphs[glyphStringIndex - 1] 1525 glyphStringAbove += glyphString 1526 self.RemoveGlyphString(glyphString) 1527 self.UpdateGlyphString(glyphStringAbove) 1528 cursorAdvance = -1 1529 self._ResetCurrentLen() 1530 else: 1531 currentParams = self._activeParams.Copy() 1532 unichar = unichr(char) 1533 fontFamily = uicore.font.GetFontFamilyBasedOnClientLanguageID() 1534 currentParams.fontFamily = fontFamily 1535 uicore.font.ResolveFontFamily(currentParams) 1536 if currentParams.url: 1537 if 'loc' in currentParams: 1538 currentParams.pop('loc') 1539 nextParams = self.GetNextParams(glyphString, stackCursorIndex) 1540 cursorOffset = 1 1541 while nextParams and 'loc' in nextParams and nextParams.url is not None: 1542 nextParams.pop('loc') 1543 nextParams = self.GetNextParams(glyphString, stackCursorIndex + cursorOffset) 1544 cursorOffset += 1 1545 1546 prevParams = self.GetPriorParams(glyphString, stackCursorIndex) 1547 cursorOffset = -1 1548 while prevParams and 'loc' in prevParams and prevParams.url is not None: 1549 prevParams.pop('loc') 1550 prevParams = self.GetPriorParams(glyphString, stackCursorIndex + cursorOffset) 1551 cursorOffset -= 1 1552 1553 if unichar in ENDLINKCHARS: 1554 nextParams = self.GetNextParams(glyphString, stackCursorIndex) 1555 if not nextParams or nextParams.url != currentParams.url: 1556 currentParams = currentParams.Copy() 1557 currentParams.url = None 1558 self.ResetToPreviousUrlParams(currentParams, glyphString, stackCursorIndex) 1559 elif stackCursorIndex == 0: 1560 currentParams = currentParams.Copy() 1561 currentParams.url = None 1562 self.ResetToPreviousUrlParams(currentParams, glyphString, stackCursorIndex) 1563 self.InsertToGlyphString(glyphString, currentParams, unichar, stackCursorIndex) 1564 self.UpdateGlyphString(glyphString, advance=1, stackCursorIndex=stackCursorIndex) 1565 cursorAdvance = 1 1566 self.IncreaseCurrentLen() 1567 self.UpdatePosition() 1568 self.OnChange() 1569 self.SetCursorPos(self.globalCursorPos + cursorAdvance) 1570 self.CheckHintText() 1571 self.SetSelectionInitPos(self.globalCursorPos) 1572 node = self.GetActiveNode() 1573 if node: 1574 self.ShowNodeIdx(node.idx) 1575 if setParams: 1576 self._activeParams = setParams.Copy() 1577 self.UpdateAttributePanel() 1578 1579 def DecreaseCurrentLen(self): 1580 self._AddOrRemoveOneFromCurrentLen(-1) 1581 1582 def IncreaseCurrentLen(self): 1583 self._AddOrRemoveOneFromCurrentLen(1) 1584 1585 def _AddOrRemoveOneFromCurrentLen(self, valueChange): 1586 if self._currentLen is None: 1587 self._currentLen = self.GetLenghtOfCurrentText() 1588 else: 1589 self._currentLen = max(0, self._currentLen + valueChange) 1590 1591 def _ResetCurrentLen(self): 1592 self._currentLen = None 1593 1594 def ResetToPreviousUrlParams(self, param, glyphString, stackCursorIndex): 1595 previousParams = None 1596 while stackCursorIndex: 1597 previousParams = self.GetPriorParams(glyphString, stackCursorIndex) 1598 stackCursorIndex = stackCursorIndex - 1 1599 1600 if not previousParams or previousParams.url: 1601 param.color = self.defaultFontColor 1602 param.bold = False 1603 param.italic = False 1604 param.underline = False 1605 elif previousParams: 1606 param.color = previousParams.color 1607 param.bold = previousParams.bold 1608 param.italic = previousParams.italic 1609 param.underline = previousParams.underline 1610 1611 def GetPriorParams(self, glyphString, stackCursorIndex): 1612 if len(glyphString): 1613 paramsInFront = glyphString[max(0, min(len(glyphString) - 1, stackCursorIndex - 1))][-1] 1614 return paramsInFront 1615 glyphStringIndex = self.GetGlyphStringIndex(glyphString) 1616 while glyphStringIndex > 0: 1617 glyphString = self.sr.paragraphs[glyphStringIndex - 1] 1618 if len(glyphString): 1619 return glyphString[-1][-1] 1620 glyphStringIndex = self.GetGlyphStringIndex(glyphString) 1621 1622 def GetNextParams(self, glyphString, stackCursorIndex): 1623 if len(glyphString) > stackCursorIndex: 1624 paramsBehind = glyphString[stackCursorIndex][-1] 1625 return paramsBehind 1626 while glyphString is not self.sr.paragraphs[-1]: 1627 glyphStringIndex = self.GetGlyphStringIndex(glyphString) 1628 glyphString = self.sr.paragraphs[glyphStringIndex + 1] 1629 if len(glyphString): 1630 return glyphString[0][-1] 1631 1632 def ReloadGlyphString(self, glyphString): 1633 data = [ (params, char) for advance, align, color, sbit, char, asc, des, params in glyphString ] 1634 del glyphString[:] 1635 for idx, (params, char) in enumerate(data): 1636 self.InsertToGlyphString(glyphString, params, char, idx) 1637 1638 def RemoveFromGlyphString(self, glyphString, startIdx, endIdx): 1639 glyphString.Remove(startIdx, endIdx) 1640 1641 @telemetry.ZONE_METHOD 1642 def InsertToGlyphString(self, glyphString, params, text, idx): 1643 if params.url: 1644 Link().FormatLinkParams(params, linkStyle=self.linkStyle) 1645 glyphString.Insert(params, text, idx) 1646 1647 def OnClipperResize(self, width, height): 1648 self.RefreshNodes() 1649 self.DoSizeUpdateDelayed() 1650 1651 def OnChange(self, *args): 1652 pass 1653 1654 def GetSelection(self): 1655 return self.HasSelection() 1656 1657 def HasSelection(self): 1658 fromIdx, toIdx = self.globalSelectionRange 1659 return fromIdx != toIdx 1660 1661 def DeleteSelected(self): 1662 fromIdx, toIdx = self.globalSelectionRange 1663 if fromIdx == toIdx: 1664 return 1665 startGS = None 1666 endGS = None 1667 singleLineGS = None 1668 rmCompletely = [] 1669 self._ResetCurrentLen() 1670 counter = 0 1671 for gsIdx, glyphString in enumerate(self.sr.paragraphs): 1672 gsLength = len(glyphString) 1673 if fromIdx <= counter and counter + gsLength <= toIdx: 1674 rmCompletely.append(glyphString) 1675 elif counter <= fromIdx <= counter + gsLength and counter <= toIdx <= counter + gsLength: 1676 singleLineGS = glyphString 1677 self.RemoveFromGlyphString(glyphString, fromIdx - counter, toIdx - counter) 1678 elif counter <= fromIdx <= counter + gsLength: 1679 startGS = glyphString 1680 startGS.FlushFromIndex(fromIdx - counter) 1681 elif counter <= toIdx <= counter + gsLength: 1682 endGS = glyphString 1683 glyphString.FlushToIndex(toIdx - counter) 1684 counter += gsLength 1685 counter += 1 1686 if counter >= toIdx: 1687 break 1688 1689 if singleLineGS is not None: 1690 self.UpdateGlyphString(singleLineGS) 1691 elif startGS and endGS: 1692 startGS += endGS 1693 rmCompletely.append(endGS) 1694 self.UpdateGlyphString(startGS) 1695 elif startGS: 1696 self.UpdateGlyphString(startGS) 1697 elif endGS: 1698 self.UpdateGlyphString(endGS) 1699 for glyphString in rmCompletely: 1700 self.RemoveGlyphString(glyphString) 1701 1702 if not self.sr.nodes: 1703 self.SetValue('') 1704 else: 1705 self.SetSelectionRange(None, None, updateCursor=False) 1706 self.SetCursorPos(fromIdx) 1707 self.SetSelectionInitPos(self.globalCursorPos) 1708 self.UpdatePosition() 1709 self.OnChange() 1710 return 1 1711 1712 def GetSeletedCharData(self): 1713 fromIdx, toIdx = self.globalSelectionRange 1714 if fromIdx == toIdx: 1715 return 1716 startedCollection = False 1717 done = False 1718 ret = [] 1719 counter = 0 1720 for gsIdx, glyphString in enumerate(self.sr.paragraphs): 1721 gData = [] 1722 for cdIdx, charData in enumerate(glyphString): 1723 if counter == fromIdx: 1724 startedCollection = True 1725 if startedCollection: 1726 gData.append((cdIdx, charData)) 1727 counter += 1 1728 if counter == toIdx: 1729 done = True 1730 break 1731 1732 if counter == fromIdx: 1733 startedCollection = True 1734 if startedCollection: 1735 ret.append((glyphString, gData)) 1736 if done: 1737 break 1738 counter += 1 1739 if counter == toIdx: 1740 done = True 1741 break 1742 1743 return ret 1744 1745 def ApplySelection(self, what, data = None, toggle = False): 1746 selectedData = self.GetSeletedCharData() 1747 if selectedData: 1748 node = self.GetActiveNode() 1749 stackCursorIndex = self.globalCursorPos - node.startCursorIndex + node.stackCursorIndex 1750 prevParams = self.GetPriorParams(node.glyphString, stackCursorIndex) 1751 if prevParams: 1752 params = prevParams.Copy() 1753 else: 1754 params = self.GetFontParams() 1755 changeGS = [] 1756 changeParams = [] 1757 for glyphString, gsData in selectedData: 1758 for charIdx, (advance, align, color, sbit, char, asc, des, _params) in gsData: 1759 changeParams.append(_params) 1760 1761 changeGS.append(glyphString) 1762 1763 anchor = self.ApplyGameSelection(what, data, changeParams) 1764 if anchor == -1: 1765 anchor = None 1766 bBalance = 0 1767 uBalance = 0 1768 iBalance = 0 1769 for obj in changeParams: 1770 bBalance += 1 if obj.bold else -1 1771 uBalance += 1 if obj.underline else -1 1772 iBalance += 1 if obj.italic else -1 1773 1774 params.bold = 1 if bBalance > 0 else 0 1775 params.italic = 1 if iBalance > 0 else 0 1776 params.underline = 1 if uBalance > 0 else 0 1777 if toggle: 1778 params.bold = not params.bold 1779 params.italic = not params.italic 1780 params.underline = not params.underline 1781 for obj in changeParams: 1782 if what == 1: 1783 obj.bold = params.bold 1784 elif what == 2: 1785 obj.italic = params.italic 1786 elif what == 3: 1787 obj.underline = params.underline 1788 elif what == 4: 1789 obj.color = data 1790 elif what == 5: 1791 obj.fontsize = data 1792 elif what == 6 and anchor: 1793 obj.url = anchor 1794 obj.loc = 1 1795 1796 for glyphString in changeGS: 1797 self.ReloadGlyphString(glyphString) 1798 self.UpdateGlyphString(glyphString) 1799 1800 self._activeParams = params.Copy() 1801 self.UpdateAttributePanel() 1802 self.UpdatePosition() 1803 fromIdx, toIdx = self.globalSelectionRange 1804 self.SetCursorPos(max(fromIdx, toIdx)) 1805 uicore.registry.SetFocus(self) 1806 else: 1807 self.UpdateAttributePanel() 1808 if what == 6: 1809 self.SetSelectionRange(None, None, updateCursor=False) 1810 self.SetSelectionInitPos(self.globalCursorPos) 1811 self.RemoveAnchor() 1812 self.RefreshCursorAndSelection(updateActiveParams=False) 1813 self.UpdatePosition() 1814 1815 def ApplyGameSelection(self, *args): 1816 pass 1817 1818 def OnUp(self): 1819 pass 1820 1821 def OnDown(self): 1822 pass 1823 1824 def OnHome(self): 1825 pass 1826 1827 def OnEnd(self): 1828 pass 1829 1830 def CtrlUp(self, *args): 1831 pass 1832 1833 def CtrlDown(self, *args): 1834 pass 1835 1836 @telemetry.ZONE_METHOD 1837 def DoSizeUpdateDelayed(self): 1838 if not self.sr.nodes: 1839 return 1840 try: 1841 self._DoSizeUpdateDelayed() 1842 except TaskletExit: 1843 pass 1844 except Exception as e: 1845 raise 1846 1847 @telemetry.ZONE_METHOD 1848 def _DoSizeUpdateDelayed(self): 1849 if self.resizeTasklet: 1850 self.resizeTasklet.kill() 1851 self.resizeTasklet = None 1852 self.resizeTasklet = uthread.new(self.DoSizeUpdate) 1853 1854 @telemetry.ZONE_METHOD 1855 def DoSizeUpdate(self): 1856 if self.destroyed: 1857 return 1858 self.resizing = 1 1859 nodesByGlyphstringIDs = self.GetDictNodesByGlyphStringIDs() 1860 for glyphString in self.sr.paragraphs: 1861 self.UpdateGlyphString(glyphString, 0, 0, nodesByGlyphstringIDs=nodesByGlyphstringIDs) 1862 1863 self.RefreshNodes() 1864 if self.autoScrollToBottom and self.GetScrollProportion() == 0.0: 1865 self.ScrollToProportion(1.0) 1866 else: 1867 self.UpdatePosition() 1868 self.RefreshCursorAndSelection() 1869 self.resizeTasklet = None 1870 self.resizing = 1 1871 1872 @telemetry.ZONE_METHOD 1873 def RefreshNodeIndexes(self, fromWhere = None): 1874 if self.destroyed: 1875 return 1876 for nodeidx, node in enumerate(self.sr.nodes): 1877 node.idx = nodeidx 1878 1879 @telemetry.ZONE_METHOD 1880 def GetNodeHeight(self, node, clipperWidth): 1881 if node.idx == 0: 1882 xtraHeight = TEXTSIDEMARGIN 1883 else: 1884 xtraHeight = TEXTLINEMARGIN 1885 node.height = self.ReverseScaleDpi(node._baseHeight) + xtraHeight 1886 return node.height 1887 1888 def EnlargeSize(self): 1889 if self.sr.attribPanel: 1890 self.sr.attribPanel.sr.comboFontSize.ShiftVal(1, 1) 1891 self.UpdateCharacterCounter() 1892 self.OnChange() 1893 1894 def ReduceSize(self): 1895 if self.sr.attribPanel: 1896 self.sr.attribPanel.sr.comboFontSize.ShiftVal(-1, 1) 1897 self.UpdateCharacterCounter() 1898 self.OnChange() 1899 1900 def ToggleBold(self): 1901 if not self.allowFormatChanges: 1902 return 1903 self._ResetCurrentLen() 1904 self._activeParams.bold = not self._activeParams.bold 1905 self.ApplySelection(1, toggle=True) 1906 self.UpdateCharacterCounter() 1907 self.OnChange() 1908 1909 def ToggleUnderline(self): 1910 if not self.allowFormatChanges: 1911 return 1912 self._ResetCurrentLen() 1913 self._activeParams.underline = not self._activeParams.underline 1914 self.ApplySelection(3, toggle=True) 1915 self.UpdateCharacterCounter() 1916 self.OnChange() 1917 1918 def ToggleItalic(self): 1919 if not self.allowFormatChanges: 1920 return 1921 self._ResetCurrentLen() 1922 self._activeParams.italic = not self._activeParams.italic 1923 self.ApplySelection(2, toggle=True) 1924 self.UpdateCharacterCounter() 1925 self.OnChange() 1926 1927 def ChangeFontSize(self, newSize): 1928 self._ResetCurrentLen() 1929 uicore.registry.SetFocus(self) 1930 self._activeParams.fontsize = newSize 1931 self.ApplySelection(5, newSize) 1932 self.UpdateCharacterCounter() 1933 self.OnChange() 1934 1935 def ChangeFontColor(self, newColor): 1936 self._ResetCurrentLen() 1937 uicore.registry.SetFocus(self) 1938 self._activeParams.color = newColor 1939 self.ApplySelection(4, newColor) 1940 self.UpdateCharacterCounter() 1941 self.OnChange() 1942 1943 def AddAnchor(self): 1944 self._ResetCurrentLen() 1945 uicore.registry.SetFocus(self) 1946 self.RemoveAnchor() 1947 self.ApplySelection(6) 1948 self.OnChange() 1949 1950 def RemoveAnchor(self): 1951 self._ResetCurrentLen() 1952 if self._activeParams.url: 1953 self._activeParams.url = None 1954 if 'loc' in self._activeParams: 1955 self._activeParams.pop('loc') 1956 node = self.GetActiveNode() 1957 stackCursorIndex = self.globalCursorPos - node.startCursorIndex + node.stackCursorIndex 1958 glyphString = node.glyphString 1959 self.ResetToPreviousUrlParams(self._activeParams, glyphString, stackCursorIndex) 1960 self.OnChange() 1961 1962 def HasText(self): 1963 for each in self.sr.paragraphs: 1964 if each: 1965 return True 1966 1967 return False 1968 1969 def Disable(self): 1970 Scroll.Disable(self) 1971 self.opacity = 0.3 1972 1973 def Enable(self): 1974 Scroll.Enable(self) 1975 self.opacity = 1.0 1976 1977 1978 class SE_EditTextlineCore(SE_BaseClassCore, Link): 1979 __guid__ = 'uicls.SE_EditTextlineCore' 1980 default_showHilite = False 1981 sound_hover = None 1982 1983 def ApplyAttributes(self, attributes): 1984 SE_BaseClassCore.ApplyAttributes(self, attributes) 1985 self.selectingtext = 0 1986 self.advanceByIndex = [] 1987 self._currentWidth = 0 1988 self.sr.textcursor = None 1989 self.sr.cursortimer = None 1990 self.sr.textselection = None 1991 trinity.device.RegisterResource(self) 1992 uicore.textObjects.add(self) 1993 1994 def OnChar(self, *args): 1995 pass 1996 1997 @telemetry.ZONE_METHOD 1998 def Load(self, node): 1999 self.RenderLine() 2000 if node.idx == 0: 2001 xtraHeight = TEXTSIDEMARGIN 2002 else: 2003 xtraHeight = TEXTLINEMARGIN 2004 if self.height != self.ReverseScaleDpi(node._baseHeight) + xtraHeight: 2005 node.height = None 2006 node.scroll.UpdatePosition() 2007 self.UpdateSelectionHilite() 2008 self.UpdateCursor() 2009 2010 def GetSprite(self): 2011 if self.sr.sprite is None: 2012 self.sr.sprite = Sprite(parent=self, state=uiconst.UI_PICKCHILDREN, spriteEffect=trinity.TR2_SFX_FONT, idx=0) 2013 return self.sr.sprite 2014 2015 def OnCreate(self, dev): 2016 if self.sr.sprite: 2017 self.sr.sprite.texture = None 2018 if self.sr and self.sr.node: 2019 self.Load(self.sr.node) 2020 2021 @telemetry.ZONE_METHOD 2022 def RenderLine(self, createLinks = True): 2023 if createLinks and self.sr.links: 2024 self.sr.links.Flush() 2025 node = self.sr.node 2026 if node.idx == 0: 2027 xtraHeight = TEXTSIDEMARGIN 2028 else: 2029 xtraHeight = TEXTLINEMARGIN 2030 self.advanceByIndex = [] 2031 self._currentWidth = 0 2032 if node._startIndex is None or not node.glyphString or node._width == 0 or node._baseHeight == 0: 2033 if self.sr.sprite: 2034 self.sr.sprite.state = uiconst.UI_HIDDEN 2035 return 2036 sprite, surf = self.GetSurface(node._width, node._baseHeight) 2037 if not surf: 2038 return 2039 sprite.left = TEXTSIDEMARGIN 2040 sprite.top = xtraHeight 2041 k = surf.LockBuffer(None, False) 2042 try: 2043 buf = SE_TextlineCore.TexResBuf(k) 2044 trinity.fontMan.ClearBuffer(buf.data, buf.width, buf.height, buf.pitch) 2045 self.DrawLine(node.glyphString, buf, 0, node._baseHeight - node._baseLine, startIdx=node._startIndex, endIdx=node._endIndex, createLinks=createLinks) 2046 finally: 2047 surf.UnlockBuffer() 2048 2049 sprite.state = uiconst.UI_DISABLED 2050 2051 def GetUrlText(self, glyphString, startIdx, url): 2052 retText = '' 2053 for t in reversed(glyphString[:startIdx]): 2054 advance, align, color, sbit, char, asc, des, params = t 2055 if params.url == url: 2056 retText = char + retText 2057 else: 2058 break 2059 2060 for t in glyphString[startIdx:]: 2061 advance, align, color, sbit, char, asc, des, params = t 2062 if params.url != url: 2063 return retText 2064 retText += char 2065 2066 return retText 2067 2068 @telemetry.ZONE_METHOD 2069 def DrawLine(self, glyphString, buf, bx0, by0, startIdx, endIdx, createLinks = True): 2070 sprite = self.GetSprite() 2071 x = 0 2072 self.advanceByIndex.append(x) 2073 openLink = None 2074 lastParams = None 2075 i = 0 2076 for t in glyphString[startIdx:endIdx]: 2077 advance, align, color, sbit, char, asc, des, params = t 2078 if lastParams and lastParams.url != params.url: 2079 openLink = None 2080 currentIdx = startIdx + i 2081 if params.url: 2082 if createLinks and (not lastParams or lastParams.url != params.url): 2083 openLink = self.CreateLink(params.url) 2084 openLink.left = uicore.ReverseScaleDpi(x) + sprite.left 2085 openLink.height = self.height 2086 openLink.top = sprite.top 2087 openLink.linkText = self.GetUrlText(glyphString, currentIdx, params.url) 2088 openLink.startIdx = currentIdx 2089 if self.sr.node.hiliteLink == params.url: 2090 hiliteRange = self.sr.node.hiliteRange 2091 if hiliteRange and not hiliteRange[0] <= currentIdx <= hiliteRange[1]: 2092 linkState = uiconst.LINK_IDLE 2093 else: 2094 linkState = uiconst.LINK_HOVER 2095 else: 2096 linkState = uiconst.LINK_IDLE 2097 params = params.Copy() 2098 Link().FormatLinkParams(params, linkState, linkStyle=self.sr.node.scroll.linkStyle) 2099 color = params.color or -1 2100 if type(color) != types.IntType: 2101 tricol = trinity.TriColor(*color) 2102 color = tricol.AsInt() 2103 if params.underline: 2104 extraSpace = advance - sbit.xadvance 2105 sbit.ToBufferWithUnderline(buf.data, buf.width, buf.height, buf.pitch, x + bx0, by0, color, extraSpace) 2106 else: 2107 sbit.ToBuffer(buf.data, buf.width, buf.height, buf.pitch, x + bx0, by0, color) 2108 if openLink: 2109 openLink.width = uicore.ReverseScaleDpi(x + advance) - openLink.left + sprite.left 2110 openLink.endIdx = currentIdx 2111 lastParams = params 2112 x += advance 2113 self.advanceByIndex.append(self.ReverseScaleDpi(x)) 2114 i += 1 2115 2116 self._currentWidth = self.ReverseScaleDpi(x) 2117 2118 @telemetry.ZONE_METHOD 2119 def GetSurface(self, width, height): 2120 sprite = self.GetSprite() 2121 if sprite.texture and sprite.texture.atlasTexture: 2122 textureSize = (sprite.texture.atlasTexture.width, sprite.texture.atlasTexture.height) 2123 else: 2124 textureSize = (0, 0) 2125 sprite.useSizeFromTexture = True 2126 surf = None 2127 try: 2128 if textureSize != (width, height): 2129 texturePrimary = trinity.Tr2Sprite2dTexture() 2130 texturePrimary.atlasTexture = uicore.uilib.CreateTexture(width, height) 2131 sprite.texture = texturePrimary 2132 surf = texturePrimary.atlasTexture 2133 else: 2134 surf = sprite.texture.atlasTexture 2135 return (sprite, surf) 2136 except Exception as e: 2137 log.LogWarn('Failed to create surface', e) 2138 log.LogException() 2139 return (sprite, None) 2140 2141 def CreateLink(self, url): 2142 if not self.sr.links: 2143 self.sr.links = Container(name='links', parent=self) 2144 link = Link(parent=self.sr.links, align=uiconst.RELATIVE, state=uiconst.UI_NORMAL) 2145 link.OnDblClick = self.OnDblClick 2146 link.OnMouseEnter = (self.LinkEnter, link) 2147 link.OnMouseExit = (self.LinkExit, link) 2148 link.cursor = uiconst.UICORSOR_FINGER 2149 link.url = url 2150 link.name = 'textlink' 2151 link.linkText = '' 2152 link.URLHandler = self.sr.node.Get('URLHandler', None) 2153 link.GetHint = lambda *args: link.GetStandardLinkHint(url) 2154 return link 2155 2156 def LinkDown(self, link, *args): 2157 link.OnMouseDown.__func__() 2158 self.HiliteLink(link) 2159 2160 def LinkUp(self, link, *args): 2161 if uicore.uilib.mouseOver is link: 2162 link.OnMouseUp.__func__() 2163 self.LinkEnter(link) 2164 2165 def LinkEnter(self, link, *args): 2166 browser = GetBrowser(self) 2167 if browser and browser.sr.window and hasattr(browser.sr.window, 'ShowHint'): 2168 browser.sr.window.ShowHint(link.hint or link.url) 2169 PlaySound(uiconst.SOUND_BUTTON_HOVER) 2170 self.HiliteLink(link) 2171 2172 def LinkExit(self, link, *args): 2173 browser = GetBrowser(self) 2174 if browser and browser.sr.window and hasattr(browser.sr.window, 'ShowHint'): 2175 browser.sr.window.ShowHint('') 2176 self.HiliteLink(link, False) 2177 2178 def HiliteLink(self, link, doHilite = True): 2179 linkUrl = None 2180 if link and doHilite: 2181 linkUrl = link.url 2182 for entry in self.sr.node.scroll.sr.nodes: 2183 entry.hiliteLink = linkUrl 2184 if not entry.panel: 2185 continue 2186 linksCont = entry.panel.sr.Get('links', None) 2187 if linksCont and linksCont == getattr(link, 'parent', None): 2188 startIdx = getattr(link, 'startIdx', None) 2189 endIdx = getattr(link, 'endIdx', None) 2190 if startIdx is not None and endIdx is not None: 2191 entry.hiliteRange = (startIdx, endIdx) 2192 else: 2193 entry.hiliteRange = None 2194 entry.panel.RenderLine(createLinks=False) 2195 2196 def SelectionHandlerDelegate(self, funcName, args): 2197 handler = self.sr.node.Get('SelectionHandler', None) 2198 if handler: 2199 func = getattr(handler, funcName, None) 2200 if func: 2201 return func(*args) 2202 2203 def GetMenu(self): 2204 self.sr.node.scroll.ShowHint('') 2205 return self.SelectionHandlerDelegate('GetMenuDelegate', (self.sr.node,)) 2206 2207 def OnMouseDown(self, button, *args): 2208 if button == uiconst.MOUSELEFT: 2209 self.SelectionHandlerDelegate('OnMouseDownDelegate', (self.sr.node,)) 2210 elif button == uiconst.MOUSERIGHT and not self.sr.node.scroll.HasSelection(): 2211 self.SelectionHandlerDelegate('OnMouseDownDelegate', (self.sr.node,)) 2212 2213 def OnMouseUp(self, button, *args): 2214 if button == uiconst.MOUSELEFT: 2215 self.SelectionHandlerDelegate('OnMouseUpDelegate', (self.sr.node,)) 2216 2217 def OnDropData(self, dragObj, nodes): 2218 self.SelectionHandlerDelegate('OnDropDataDelegate', (self.sr.node, nodes)) 2219 2220 def OnDragMove(self, nodes, *args): 2221 self.SelectionHandlerDelegate('OnDragMoveDelegate', (self.sr.node, nodes)) 2222 2223 def OnDragEnter(self, dragObj, nodes): 2224 self.SelectionHandlerDelegate('OnDragExitDelegate', (self.sr.node, nodes)) 2225 2226 def OnDragExit(self, dragObj, nodes): 2227 self.SelectionHandlerDelegate('OnDragExitDelegate', (self.sr.node, nodes)) 2228 2229 def OnClick(self, *args): 2230 pass 2231 2232 def OnDblClick(self, *args): 2233 self.SelectionHandlerDelegate('SelectWordUnderCursor', ()) 2234 2235 def OnTripleClick(self, *args): 2236 self.SelectionHandlerDelegate('SelectLineUnderCursor', ()) 2237 2238 def GetScrollAbove(self): 2239 item = self.parent 2240 while item: 2241 if isinstance(item, Scroll): 2242 return item 2243 item = item.parent 2244 2245 @telemetry.ZONE_METHOD 2246 def UpdateSelectionHilite(self): 2247 if not self.sr.node: 2248 return 2249 scrollAbove = self.GetScrollAbove() 2250 f = uicore.registry.GetFocus() 2251 if not scrollAbove or scrollAbove is not f or not trinity.app.IsActive(): 2252 selectionAlpha = 0.125 2253 else: 2254 selectionAlpha = 0.25 2255 if self.sr.node.selectionStartIndex is not None: 2256 if self.sr.textselection is None: 2257 self.sr.textselection = Fill(parent=self, align=uiconst.TOPLEFT, state=uiconst.UI_HIDDEN) 2258 self.sr.textselection.SetAlpha(selectionAlpha) 2259 if self.sr.node.startCursorIndex <= self.sr.node.selectionStartIndex <= self.sr.node.endCursorIndex: 2260 left = self.GetWidthToGlobalIndex(self.sr.node.selectionStartIndex) 2261 else: 2262 left = 0 2263 if self.sr.node.startCursorIndex <= self.sr.node.selectionEndIndex <= self.sr.node.endCursorIndex: 2264 width = self.GetWidthToGlobalIndex(self.sr.node.selectionEndIndex) 2265 elif len(self.sr.node.glyphString): 2266 width = getattr(self, '_currentWidth', 0) or self.GetSprite().width 2267 else: 2268 width = 0 2269 self.sr.textselection.left = left + TEXTSIDEMARGIN 2270 self.sr.textselection.width = max(2, width - left) 2271 self.sr.textselection.top = TEXTLINEMARGIN 2272 self.sr.textselection.height = self.height 2273 self.sr.textselection.state = uiconst.UI_DISABLED 2274 elif self.sr.textselection: 2275 self.sr.textselection.state = uiconst.UI_HIDDEN 2276 2277 def GetInternalCursorPos(self): 2278 if not self.sr.sprite: 2279 return 0 2280 l, t, w, h = self.sr.sprite.GetAbsolute() 2281 toCursorPos = uicore.uilib.x - l 2282 idx = 0 2283 w = 0 2284 for charData in self.sr.node.glyphString[self.sr.node.stackCursorIndex:self.sr.node.stackCursorIndex + self.sr.node.letterCountInLine]: 2285 adv = charData[0] 2286 if uicore.ReverseScaleDpi(w + adv) >= toCursorPos: 2287 break 2288 w += adv 2289 idx += 1 2290 2291 return idx 2292 2293 @telemetry.ZONE_METHOD 2294 def UpdateCursor(self): 2295 if self.sr.node.globalCursorPos is not None: 2296 self.GetSprite() 2297 if self.sr.textcursor is None: 2298 self.sr.textcursor = Caret(name='caret', parent=self, align=uiconst.TOPLEFT, state=uiconst.UI_HIDDEN) 2299 left = self.GetWidthToGlobalIndex(self.sr.node.globalCursorPos) 2300 if self.sr.node.idx == 0: 2301 margin = TEXTSIDEMARGIN 2302 else: 2303 margin = TEXTLINEMARGIN 2304 self.sr.textcursor.top = margin 2305 self.sr.textcursor.left = left + TEXTSIDEMARGIN 2306 self.sr.textcursor.height = self.height - margin 2307 if self.sr.cursortimer is None: 2308 self.CursorBlink() 2309 elif self.sr.textcursor: 2310 self.sr.cursortimer = None 2311 self.sr.textcursor.state = uiconst.UI_HIDDEN 2312 2313 def GetWidthToGlobalIndex(self, globalCursorPos): 2314 localIndex = globalCursorPos - self.sr.node.startCursorIndex 2315 if len(self.advanceByIndex) > localIndex: 2316 return self.advanceByIndex[localIndex] 2317 return 0 2318 2319 def GetCursorOffset(self): 2320 if self.sr.textcursor: 2321 return self.sr.textcursor.left 2322 2323 def CursorBlink(self): 2324 f = uicore.registry.GetFocus() 2325 if f is uicore.desktop or not trinity.app.IsActive(): 2326 if self.sr.textcursor: 2327 self.sr.textcursor.state = uiconst.UI_HIDDEN 2328 self.sr.cursortimer = None 2329 return 2330 if f and self.IsUnder(f) and self.sr.node.globalCursorPos is not None and self.sr.textcursor is not None: 2331 self.sr.textcursor.state = [uiconst.UI_HIDDEN, uiconst.UI_DISABLED][self.sr.textcursor.state == uiconst.UI_HIDDEN] 2332 if self.sr.cursortimer is None: 2333 self.sr.cursortimer = AutoTimer(interval=uiconst.CARET_BLINK_INTERVAL_MS, method=self.CursorBlink) 2334 else: 2335 self.sr.cursortimer = None 2336 self.sr.textcursor.state = uiconst.UI_HIDDEN 2337 2338 def GetCopyData(self, fromIdx, toIdx): 2339 return uicore.font.GetNodeCopyData(self.sr.node, fromIdx, toIdx) 2340 2341 def GetText(self, node): 2342 if node.rawText: 2343 return node.rawText 2344 text = ''.join([ glyphData[4] for glyphData in self.sr.node.glyphString[self.sr.node.stackCursorIndex:self.sr.node.stackCursorIndex + self.sr.node.letterCountInLine] ]) 2345 node.rawText = text 2346 return text