NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

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'&gt;', u'>').replace(u'&lt;', u'<').replace(u'&amp;', u'&').replace(u'&GT;', u'>').replace(u'&LT;', u'<')
    293 
    294     def Decode(self, text):
    295         return text.replace(u'&', u'&amp;').replace(u'<', u'&lt;').replace(u'>', u'&gt;')
    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