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

edit.py (78755B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\carbonui\control\edit.py
      2 from __future__ import print_function
      3 import errno
      4 import sys
      5 import blue
      6 import carbonui.const as uiconst
      7 import localization
      8 import log
      9 import telemetry
     10 import urllib2
     11 import urlparse
     12 import uthread
     13 from carbon.client.script.util.resLoader import ResFile
     14 from carbon.common.script.util.commonutils import StripTags
     15 from carbon.common.script.util.timerstuff import AutoTimer
     16 from carbonui import fontflags
     17 from carbonui.control.basicDynamicScroll import Scroll
     18 from carbonui.control.editutils import parser
     19 from carbonui.control.editutils.parserbase import ParserBaseCoreOverride as ParserBase
     20 from carbonui.control.scrollentries import SE_SpaceCore as SE_Space
     21 from carbonui.control.scrollentries import SE_TextlineCore, ScrollEntryNode
     22 from carbonui.fontconst import DEFAULT_LETTERSPACE, DEFAULT_FONTSIZE
     23 from carbonui.primitives.container import Container
     24 from carbonui.primitives.fill import Fill
     25 from carbonui.primitives.sprite import Sprite
     26 from carbonui.text.color import TextColor
     27 from carbonui.text.settings import check_convert_font_size
     28 from carbonui.uicore import uicore
     29 from carbonui.util.bunch import Bunch
     30 from carbonui.util.various_unsorted import ParseHTMLColor, GetClipboardData
     31 from menu import MenuLabel
     32 from player_messaging.client.ui_message import prompt_player_fsd_dialog
     33 
     34 class EditCore(ParserBase, Scroll):
     35     default_name = 'edit_multiline'
     36     default_align = uiconst.TOTOP
     37     default_width = 0
     38     default_height = 100
     39     default_maxLength = None
     40     default_readonly = False
     41     default_showattributepanel = 0
     42     default_hideBackground = False
     43     default_setvalue = ''
     44     default_fontsize = None
     45     default_fontcolor = TextColor.NORMAL
     46     default_letterspace = None
     47 
     48     def ApplyAttributes(self, attributes):
     49         if self.default_fontsize is None:
     50             self.default_fontsize = DEFAULT_FONTSIZE
     51         if self.default_letterspace is None:
     52             self.default_letterspace = DEFAULT_LETTERSPACE
     53         Scroll.ApplyAttributes(self, attributes)
     54         self._initingEdit = True
     55         ParserBase.Prepare(self)
     56         self.sr.currentURL = None
     57         self.sr.currentData = None
     58         self.sr.currentTXT = None
     59         self.sr.window = None
     60         self.sr.sessionreload = False
     61         self._knownWidth = None
     62         self.xmargin = 0
     63         self.ymargin = 6
     64         self.sr.resizeTimer = None
     65         self.sr.window = None
     66         self.sr.positions = {}
     67         self._selecting = 0
     68         self._showCursor = True
     69         self._maxGlobalCursorIndex = 0
     70         self.sr.startSelectionPos = (0, 0)
     71         self.sr.scrollTimer = None
     72         self.sr.attribPanel = None
     73         self.maxletters = None
     74         self.globalSelectionRange = (None, None)
     75         self.globalSelectionInitpos = None
     76         self.globalCursorPos = 0
     77         self.debug = 0
     78         self.htmldebug = 0
     79         self.IsBrowser = 1
     80         self.AttribStateChange = None
     81         self.updating = 0
     82         self.readonly = 0
     83         self.autoScrollToBottom = 0
     84         self.fontFlag = 0
     85         self.href = None
     86         fontSize = attributes.get('fontsize', self.default_fontsize)
     87         self.fontSize = self.defaultFontSize = check_convert_font_size(fontSize)
     88         self.fontColor = self.defaultFontColor = attributes.get('fontcolor', self.default_fontcolor)
     89         from carbonui.control.browser.cookieManager import CookieManager
     90         self.sr.cookieMgr = CookieManager()
     91         self.resizing = 0
     92         self.preCompText = None
     93         self.allowPrivateDrops = 0
     94         self.sr.content.GetMenu = self.GetMenuDelegate
     95         self.sr.content.OnKeyDown = self.OnKeyDown
     96         self.sr.content.OnDropData = self.OnContentDropData
     97         self.sr.content.OnMouseDown = self.OnMouseDown
     98         self.sr.content.OnMouseUp = self.OnMouseUp
     99         self.sr.content.OnMouseMove = self.OnMouseMove
    100         self.sr.content.cursor = uiconst.UICURSOR_IBEAM
    101         self._AttribStateChange()
    102         self.hideBackground = attributes.get('hideBackground', self.default_hideBackground)
    103         maxLength = attributes.get('maxLength', self.default_maxLength)
    104         self.SetMaxLength(maxLength)
    105         readonly = attributes.get('readonly', self.default_readonly)
    106         if readonly:
    107             self.ReadOnly()
    108         else:
    109             showattributepanel = attributes.get('showattributepanel', self.default_showattributepanel)
    110             if showattributepanel:
    111                 self.ShowAttributePanel()
    112             self.Editable(showpanel=showattributepanel)
    113         setvalue = attributes.get('setvalue', self.default_setvalue)
    114         self.SetValue(setvalue)
    115         if not self.destroyed:
    116             self._initingEdit = False
    117 
    118     def Prepare_Underlay_(self):
    119         self.sr.backgroundColorContainer = Container(name='backgroundColorContainer', parent=self)
    120         Scroll.Prepare_Underlay_(self)
    121 
    122     def ShowAttributePanel(self):
    123         if not self.readonly:
    124             from carbonui.control.fontAttributePanel import FontAttributePanel
    125             self.sr.attribPanel = FontAttributePanel(align=uiconst.TOTOP, padBottom=2, parent=self, idx=0, state=uiconst.UI_HIDDEN, padding=(4, 0, 4, 0))
    126 
    127     def GetLinespace(self):
    128         if not self or not getattr(self, 'sr', None):
    129             return 0
    130         if self.sr.nodes:
    131             if self.sr.nodes[-1].maxBaseHeight is not None:
    132                 return self.sr.nodes[-1].maxBaseHeight
    133             if self.sr.nodes[-1].size is not None:
    134                 return int(self.sr.nodes[-1].size)
    135         return 18
    136 
    137     def Reset(self, *args, **kw):
    138         cw, ch = self.sr.content.GetAbsoluteSize()
    139         self._knownWidth = max(100, cw)
    140         self.sr.overlays_content.Flush()
    141         self.sr.underlays_content.Flush()
    142 
    143     @telemetry.ZONE_METHOD
    144     def LoadHTML(self, htmlstr, reload = 0, scrollTo = None, newThread = 1, breakOnly = 0):
    145         if not self or self.destroyed:
    146             return
    147         if self._loading:
    148             return
    149         self._loading = 1
    150         self.sr.resizeTimer = None
    151         self.tagdepth = 0
    152         self.keybuffer = []
    153         self.textbuffers = []
    154         formdatalist = []
    155         if htmlstr is None:
    156             if self.sr.currentTXT is not None:
    157                 for form in self.sr.forms:
    158                     formdatalist.append(form.GetFields())
    159 
    160         else:
    161             self.sr.currentTXT = htmlstr
    162         htmlstr = htmlstr or self.sr.currentTXT or ''
    163         htmlstr = htmlstr.replace('*</localized>', '</localized>')
    164         self._LoadHTML(htmlstr)
    165         if self.destroyed:
    166             return
    167         if breakOnly:
    168             self._loading = 0
    169             return
    170         self.SetSelectionRange(None, None)
    171         self.RenderLines(scrollTo, formdatalist)
    172         if hasattr(self, 'CheckOverlaysAndUnderlays'):
    173             self.CheckOverlaysAndUnderlays()
    174         self.UpdateNodesCursorIndexes()
    175         if not self.readonly:
    176             self.DoContentResize()
    177             self.SetCursorPos(0)
    178         else:
    179             self.RefreshCursorAndSelection()
    180         self._loading = 0
    181 
    182     def RenderLines(self, scrollTo = None, formdatalist = []):
    183         self.sr.lines.insert(0, ScrollEntryNode(decoClass=SE_Space, height=self.ymargin))
    184         if len(self.sr.lines) == 1 and not self.readonly:
    185             self.sr.lines.append(self.GetFirstLine())
    186         self.LoadContent(contentList=self.sr.lines, scrollTo=scrollTo)
    187         if len(formdatalist) == len(self.sr.forms):
    188             for i in xrange(len(formdatalist)):
    189                 self.sr.forms[i].SetFields(formdatalist[i])
    190 
    191         if self.sr.window and hasattr(self.sr.window, 'LoadEnd'):
    192             self.sr.window.LoadEnd()
    193 
    194     def DoFontChange(self, *args):
    195         self.DoContentResize()
    196 
    197     def IsLoading(self):
    198         return self._loading
    199 
    200     def LoadBodyAttrs(self, attrs):
    201         self.sr.background.Flush()
    202         if self.hideBackground:
    203             self.RemoveActiveFrame()
    204             self.sr.backgroundColorContainer.Flush()
    205             self.HideBackground(alwaysHidden=self.hideBackground)
    206         if not attrs:
    207             return
    208         self.bgscrolltype = parser.BG_TILED
    209         for each in ('link', 'alink', 'vlink'):
    210             if getattr(attrs, each, None):
    211                 self.attrStack[-1]['%s-color' % each] = ParseHTMLColor(getattr(attrs, each, None), 1)
    212 
    213         if attrs.text:
    214             self.attrStack[-1]['color'] = ParseHTMLColor(attrs.text, 1)
    215         if attrs.bgcolor:
    216             self.attrStack[-1]['background-color'] = ParseHTMLColor(attrs.bgcolor, 1)
    217         if attrs.background:
    218             self.attrStack[-1]['background-image'] = attrs.background
    219         self.ParseStdStyles(attrs)
    220         if not self.hideBackground:
    221             if self.attrStack[-1]['background-color']:
    222                 col = self.attrStack[-1]['background-color']
    223                 Fill(parent=self.sr.backgroundColorContainer, color=col)
    224                 self.attrStack[-1]['background-color'] = None
    225             if self.attrStack[-1]['background-image']:
    226                 aL, aT, aW, aH = self.parent.GetAbsolute()
    227                 windowWidth = aW
    228                 windowHeight = aH
    229                 pic = Sprite()
    230                 pic.left = self.attrStack[-1]['background-image-left']
    231                 pic.top = self.attrStack[-1]['background-image-top']
    232                 pic.width = self.attrStack[-1]['background-image-width']
    233                 pic.height = self.attrStack[-1]['background-image-height']
    234                 if self.attrStack[-1]['background-image'].startswith('icon:'):
    235                     log.LogError('Icon:* is not legal background path')
    236                 path = self.CheckURL(self.attrStack[-1]['background-image'])
    237                 texture, tWidth, tHeight = sm.GetService('browserImage').GetTextureFromURL(path, fromWhere='Bastard::LoadBodyAttrs')
    238                 if self.destroyed:
    239                     return
    240                 pic.width = self.attrStack[-1]['background-image-width'] or tWidth
    241                 pic.height = self.attrStack[-1]['background-image-height'] or tHeight
    242                 pic.texture = texture
    243                 for pos in self.attrStack[-1]['background-position']:
    244                     if pos == 'top':
    245                         pic.top = 0
    246                     elif pos == 'bottom':
    247                         pic.left = windowHeight - pic.height
    248                     elif pos == 'middle':
    249                         pic.left = (windowHeight - pic.height) / 2
    250                     elif pos == 'left':
    251                         pic.left = 0
    252                     elif pos == 'right':
    253                         pic.left = windowWidth - pic.width
    254                     elif pos == 'center':
    255                         pic.left = (windowWidth - pic.width) / 2
    256 
    257                 if self.attrStack[-1]['background-image-color']:
    258                     pic.SetRGBA(*self.attrStack[-1]['background-image-color'])
    259                 row = Container(name='row', align=uiconst.TOTOP, pos=(0,
    260                  0,
    261                  0,
    262                  pic.height))
    263                 if self.attrStack[-1]['background-repeat'] in ('repeat', 'repeat-x'):
    264                     for x in xrange(max(windowWidth / pic.width, 2)):
    265                         row.children.append(pic.CopyTo())
    266                         pic.left += pic.width
    267 
    268                 else:
    269                     row.children.append(pic.CopyTo())
    270                 if self.attrStack[-1]['background-repeat'] in ('repeat', 'repeat-y'):
    271                     if self.attrStack[-1]['background-attachment'] == 'scroll':
    272                         self.bgscrolltype = parser.BG_TILED
    273                     for y in xrange(max(windowHeight / tHeight, 2) + 1):
    274                         self.sr.background.children.append(row.CopyTo())
    275 
    276                 else:
    277                     if self.attrStack[-1]['background-attachment'] == 'scroll':
    278                         self.bgscrolltype = parser.BG_SCROLL
    279                     self.sr.background.children.append(row.CopyTo())
    280                 if self.attrStack[-1]['background-attachment'] == 'fixed':
    281                     self.bgscrolltype = parser.BG_FIXED
    282                     self.sr.background.top = 0
    283                 self.attrStack[-1]['background-image'] = None
    284         if self.attrStack[-1]['margin-top'] != 0:
    285             margin = self.attrStack[-1]['margin-top']
    286             lpush, rpush = self.GetMargins(self.contentHeight, self.contentHeight + margin)
    287             self.FlushBuffer([([],
    288               [],
    289               [],
    290               lpush,
    291               rpush,
    292               None,
    293               margin,
    294               0,
    295               0)])
    296 
    297     def GoTo(self, URL, data = None, args = {}, scrollTo = None, newThread = 1):
    298         if self.sr.window and hasattr(self.sr.window, 'GoTo'):
    299             self.sr.window.GoTo(URL, data, args, scrollTo)
    300         else:
    301             return self._GoTo(URL, data, args, newThread=newThread)
    302 
    303     def _GoTo(self, url, data = None, args = {}, scrollTo = None, recursive = 0, newThread = 1, getSource = 0):
    304         if not url:
    305             return
    306         if getSource:
    307             return self.sr.currentTXT
    308         if self.sr.window and hasattr(self.sr.window, 'ShowLoad'):
    309             self.sr.window.ShowLoad()
    310         self.startTime = blue.os.GetWallclockTimeNow()
    311         self.SetStatus(localization.GetByLabel('/Carbon/UI/Controls/EditRichText/OpeningURL', url=url))
    312         url = url.encode('ascii')
    313         from carbonui.control.browser import browserutil
    314         from carbon.common.script.net.GPS import ClientConnectFailed, ClientConnectFailedString
    315         try:
    316             if self.sr.window and getattr(self.sr.window, 'SetCaption', None):
    317                 self.sr.window.SetCaption('')
    318             node = data
    319             self.sr.currentData = data
    320             try:
    321                 if url.startswith('./'):
    322                     url = url[2:]
    323                 if self.sr.currentURL is not None and self.sr.currentURL.lower().startswith('res:') and ':' not in url[:6]:
    324                     lastslash = self.sr.currentURL.rfind('/')
    325                     url = self.sr.currentURL[:lastslash + 1] + url
    326                 url = self.LocalizeURL(url)
    327                 if url.lower().startswith('res:'):
    328                     response = ResFile(url)
    329                     self.sr.currentURL = url
    330                 else:
    331                     url, anchor = self.CheckURL(url, getAnchor=1)
    332                     scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
    333                     self.sr.currentURL = urlparse.urlunsplit((scheme,
    334                      netloc,
    335                      path or '/',
    336                      query,
    337                      '')).rstrip()
    338                     cookie = self.sr.cookieMgr.GetCookie(url)
    339                     response = browserutil.GetStringFromURL(url, data, cookie)
    340                     if self.destroyed:
    341                         return
    342                     scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
    343                     if path and path[-1] != '/' and '.' not in path[-5:] and url.find('local://') == -1:
    344                         path += '/'
    345                     if getattr(response, 'url', None):
    346                         self.sr.currentURL = response.url
    347                     else:
    348                         self.sr.currentURL = urlparse.urlunsplit((scheme,
    349                          netloc,
    350                          path or '/',
    351                          query,
    352                          '')).rstrip()
    353                     self.sr.anchor = fragment
    354                     self.sr.sessionreload = False
    355                     self.sr.reloadtimer = None
    356                     for each in response.info().headers:
    357                         tmp = each.strip()
    358                         if tmp.lower().startswith('refresh:'):
    359                             args = tmp[8:].strip().split(';')
    360                             interval = None
    361                             if len(args) == 2:
    362                                 interval, url = args
    363                                 self.sr.reloadURL = url.replace('URL=', '').strip()
    364                             elif len(args) == 1:
    365                                 interval = args[0]
    366                                 self.sr.reloadURL = self.sr.currentURL
    367                             if interval is not None:
    368                                 if str(interval).strip().lower() == 'sessionchange':
    369                                     self.sr.sessionreload = True
    370                                 else:
    371                                     if int(interval) == 0:
    372                                         self._GoTo(self.sr.reloadURL)
    373                                         return
    374                                     self.sr.reloadtimer = AutoTimer(1000 * int(interval), self.TimedReload)
    375 
    376                     self.sr.cookieMgr.ProcessCookies(response)
    377                     self.charset = 'cp1252'
    378                     if 'content-type' in response.headers.keys():
    379                         if response.headers['content-type'].startswith('image/'):
    380                             self.LoadHTML('<HTML><BODY><IMG SRC="' + url + '"></BODY></HTML>')
    381                             return
    382                         for each in response.headers['content-type'].split(';'):
    383                             tmp = each.strip().lower()
    384                             if tmp.startswith('charset='):
    385                                 charset = tmp[8:].strip()
    386                                 try:
    387                                     import codecs
    388                                     codecs.lookup(str(charset))
    389                                     self.charset = charset
    390                                 except:
    391                                     sys.exc_clear()
    392 
    393                 pieces = []
    394                 while True:
    395                     next4kb = response.read(4096)
    396                     if next4kb:
    397                         pieces.append(next4kb)
    398                         blue.pyos.BeNice()
    399                     else:
    400                         break
    401 
    402                 txt = ''.join(pieces)
    403                 if txt[:2] == '\xff\xfe':
    404                     txt = txt.decode('utf-16')
    405                 self.LoadHTML(txt, scrollTo=scrollTo, newThread=newThread)
    406             except OSError as what:
    407                 if what.errno == errno.ENOENT:
    408                     self.LoadHTML(localization.GetByLabel('/Carbon/UI/Controls/EditRichText/HTMLErrorFileNotFound', filename=what.filename))
    409                 else:
    410                     self.LoadHTML(localization.GetByLabel('/Carbon/UI/Controls/EditRichText/HTMLErrorOSError', filename=what.filename, errorNumber=what.errno, errorString=what.strerror))
    411                 sys.exc_clear()
    412             except ClientConnectFailed as what:
    413                 s = ClientConnectFailedString(what)
    414                 if s.lower() in ('host not found', 'valid name, no data record of requested type'):
    415                     self.LoadHTML(localization.GetByLabel('/Carbon/UI/Controls/EditRichText/HTMLErrorHostNotFound'))
    416                 elif s.lower() in ('connection timed out', 'valid name, no data record of requested type'):
    417                     self.LoadHTML(localization.GetByLabel('/Carbon/UI/Controls/EditRichText/HTMLErrorConnectionTimeOut', title=what.args[0], hostAddress=url))
    418                 else:
    419                     self.LoadHTML(localization.GetByLabel('/Carbon/UI/Controls/EditRichText/HTMLErrorGeneric', title=what.args[0]))
    420                 sys.exc_clear()
    421             except urllib2.URLError as what:
    422                 self.LoadHTML(localization.GetByLabel('/Carbon/UI/Controls/EditRichText/HTMLErrorURLError', error=what))
    423                 sys.exc_clear()
    424             except ValueError as what:
    425                 self.LoadHTML(localization.GetByLabel('/Carbon/UI/Controls/EditRichText/HTMLErrorURLError', error=what))
    426                 log.LogException()
    427             except AttributeError:
    428                 if self is None or self.destroyed:
    429                     sys.exc_clear()
    430                 else:
    431                     self.HandleException()
    432             except Exception as e:
    433                 self.HandleException()
    434 
    435         finally:
    436             if self.destroyed:
    437                 return
    438             self.SetStatus('Done')
    439 
    440     def HandleException(self):
    441         log.LogException()
    442         self.LoadHTML(localization.GetByLabel('/Carbon/UI/Controls/EditRichText/HTMLErrorException'))
    443 
    444     def TimedReload(self):
    445         self.sr.reloadtimer = None
    446         self._GoTo(self.sr.reloadURL)
    447 
    448     def SessionChanged(self):
    449         if self.sr.sessionreload:
    450             self._GoTo(self.sr.reloadURL)
    451 
    452     def CloseTags(self, tag):
    453         s = ''
    454         closetags = {'u': '</u>',
    455          'b': '</b>',
    456          'i': '</i>',
    457          'a': '</a>',
    458          'color': '</font>',
    459          'size': '</font>',
    460          'url': '</url>'}
    461         t = ''
    462         flags = size = color = link = None
    463         while t != tag and len(self.tagstack):
    464             t, flags, size, color, link = self.tagstack.pop()
    465             s += closetags[t]
    466 
    467         return (s,
    468          flags,
    469          size,
    470          color,
    471          link)
    472 
    473     def SetValue(self, text, scrolltotop = 0, cursorPos = None, preformatted = 0, html = 1, fontColor = None):
    474         self.fontSize = self.defaultFontSize
    475         self.fontColor = fontColor or self.defaultFontColor
    476         self.fontFlag = 0
    477         self.href = None
    478         self._AttribStateChange()
    479         if scrolltotop:
    480             scrollTo = 0.0
    481         else:
    482             scrollTo = self.GetScrollProportion()
    483             if self.autoScrollToBottom and scrollTo == 0.0:
    484                 scrollTo = 1.0
    485         text = text or ''
    486         if not html:
    487             text = text.replace('<url=', '<a href=').replace('</url>', '</a>').replace('<fontsize', '<font size').replace('<color=0x', '<font color=#')
    488         if preformatted:
    489             self.SetPlainLoad()
    490             text = text.replace('\t', '    ')
    491         if not StripTags(text):
    492             text = ''
    493         self.LoadHTML('<html><body>%s</body></html>' % text, breakOnly=0, scrollTo=scrollTo)
    494         if cursorPos is not None and not self.readonly:
    495             self.SetCursorPos(cursorPos)
    496 
    497     SetText = SetValue
    498 
    499     def GetValue(self, html = 1):
    500         if html:
    501             ret = self.SaveHTML()
    502         else:
    503             ret = self.SaveTextMarkup()
    504         return ret
    505 
    506     def SaveHTML(self):
    507         self.Simplify()
    508         str = ''
    509         oldFontSize = self.defaultFontSize
    510         defaultColor = oldColor = (1.0, 1.0, 1.0, 0.75)
    511         oldFlags = 0
    512         oldLink = None
    513         self.tagstack = []
    514         for stack, attrs in self.textbuffers:
    515             for obj in stack:
    516                 if obj.type == '<text>':
    517                     if obj.a != oldLink and oldLink is not None:
    518                         t, oldFlags, oldFontSize, oldColor, oldLink = self.CloseTags('a')
    519                         str += t
    520                     if oldFlags & fontflags.u and not obj.fontFlags & fontflags.u:
    521                         t, oldFlags, oldFontSize, oldColor, oldLink = self.CloseTags('u')
    522                         str += t
    523                     if obj.color != oldColor and oldColor != defaultColor:
    524                         t, oldFlags, oldFontSize, oldColor, oldLink = self.CloseTags('color')
    525                         str += t
    526                     if obj.fontSize != oldFontSize and oldFontSize != self.defaultFontSize:
    527                         t, oldFlags, oldFontSize, oldColor, oldLink = self.CloseTags('size')
    528                         str += t
    529                     if oldFlags & fontflags.b and not obj.fontFlags & fontflags.b:
    530                         t, oldFlags, oldFontSize, oldColor, oldLink = self.CloseTags('b')
    531                         str += t
    532                     if oldFlags & fontflags.i and not obj.fontFlags & fontflags.i:
    533                         t, oldFlags, oldFontSize, oldColor, oldLink = self.CloseTags('i')
    534                         str += t
    535                     if not oldFlags & fontflags.i and obj.fontFlags & fontflags.i:
    536                         str += '<i>'
    537                         self.tagstack += [('i',
    538                           oldFlags,
    539                           oldFontSize,
    540                           oldColor,
    541                           oldLink)]
    542                     if not oldFlags & fontflags.b and obj.fontFlags & fontflags.b and not obj.a:
    543                         str += '<b>'
    544                         self.tagstack += [('b',
    545                           oldFlags,
    546                           oldFontSize,
    547                           oldColor,
    548                           oldLink)]
    549                     if obj.fontSize != oldFontSize and self.defaultFontSize != obj.fontSize:
    550                         str += '<font size="%d">' % obj.fontSize
    551                         self.tagstack += [('size',
    552                           oldFlags,
    553                           oldFontSize,
    554                           oldColor,
    555                           oldLink)]
    556                     if obj.color != oldColor and obj.color != defaultColor and not obj.a:
    557                         str += '<font color="#%02x%02x%02x%02x">' % (obj.color[3] * 255,
    558                          obj.color[0] * 255,
    559                          obj.color[1] * 255,
    560                          obj.color[2] * 255)
    561                         self.tagstack += [('color',
    562                           oldFlags,
    563                           oldFontSize,
    564                           oldColor,
    565                           oldLink)]
    566                     if not oldFlags & fontflags.u and obj.fontFlags & fontflags.u and not obj.a:
    567                         str += '<u>'
    568                         self.tagstack += [('u',
    569                           oldFlags,
    570                           oldFontSize,
    571                           oldColor,
    572                           oldLink)]
    573                     if obj.a != oldLink and obj.a is not None:
    574                         str += '<a href="%s">' % obj.a.href
    575                         self.tagstack += [('a',
    576                           oldFlags,
    577                           oldFontSize,
    578                           oldColor,
    579                           oldLink)]
    580                     oldFontSize = obj.fontSize
    581                     oldFlags = obj.fontFlags
    582                     oldColor = obj.color
    583                     oldLink = obj.a
    584                     str += obj.letters.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
    585 
    586             str += '<br>'
    587 
    588         str = str[:-4]
    589         t, oldFlags, oldFontSize, oldColor, oldLink = self.CloseTags('all')
    590         str += t
    591         if not StripTags(str):
    592             return ''
    593         return str
    594 
    595     def SaveTextMarkup(self):
    596         self.Simplify()
    597         retString = ''
    598         for stack, attrs in self.textbuffers:
    599             for obj in stack:
    600                 if obj.type == '<text>':
    601                     if not obj.letters:
    602                         continue
    603                     if self.defaultFontSize != obj.fontSize:
    604                         retString += '<fontsize=%s>' % obj.fontSize
    605                     if self.defaultFontColor != obj.color:
    606                         retString += '<color=0x%02x%02x%02x%02x>' % (obj.color[3] * 255,
    607                          obj.color[0] * 255,
    608                          obj.color[1] * 255,
    609                          obj.color[2] * 255)
    610                     if obj.a is not None:
    611                         retString += '<url=%s>' % obj.a.href
    612                     if obj.fontFlags & fontflags.u:
    613                         retString += '<u>'
    614                     if obj.fontFlags & fontflags.b:
    615                         retString += '<b>'
    616                     if obj.fontFlags & fontflags.i:
    617                         retString += '<i>'
    618                     retString += obj.letters.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
    619                     if obj.fontFlags & fontflags.i:
    620                         retString += '</i>'
    621                     if obj.fontFlags & fontflags.b:
    622                         retString += '</b>'
    623                     if obj.fontFlags & fontflags.u:
    624                         retString += '</u>'
    625                     if obj.a is not None:
    626                         retString += '</url>'
    627                     if self.defaultFontColor != obj.color:
    628                         retString += '</color>'
    629                     if self.defaultFontSize != obj.fontSize:
    630                         retString += '</fontsize>'
    631 
    632             retString += '<br>'
    633 
    634         retString = retString[:-4]
    635         if not StripTags(retString):
    636             return ''
    637         return retString
    638 
    639     def SetDefaultFontSize(self, size):
    640         for stack, attrs in self.textbuffers:
    641             for obj in stack:
    642                 if obj.type == '<text>' and obj.fontSize == self.defaultFontSize:
    643                     obj.fontSize = size
    644 
    645         self.defaultFontSize = self.fontSize = size
    646         self.DoContentResize()
    647 
    648     def PrintBuffer(self):
    649         print('++++++++++++++++++++')
    650         for stack, attrs in self.textbuffers:
    651             print('Stack', id(stack))
    652             for obj in stack:
    653                 if obj.type == '<text>':
    654                     print('    Object', id(obj), obj.fontSize, repr(obj.letters))
    655                 else:
    656                     print('    Object.type', repr(obj.type))
    657 
    658     def ReadOnly(self, *args):
    659         self.readonly = 1
    660         if self.sr.attribPanel is not None:
    661             self.sr.attribPanel.state = uiconst.UI_HIDDEN
    662         self.HideCursor()
    663 
    664     def Editable(self, showpanel = 1, plainload = 0, *args):
    665         self.readonly = 0
    666         if self.sr.attribPanel is not None:
    667             if showpanel:
    668                 self.sr.attribPanel.state = uiconst.UI_NORMAL
    669             else:
    670                 self.sr.attribPanel.state = uiconst.UI_HIDDEN
    671         if plainload:
    672             self.SetPlainLoad()
    673         self.ShowCursor()
    674 
    675     def SetPlainLoad(self):
    676         self.plainload = 1
    677         self.GetTextObject = self.GetTextObject_Overwrite
    678 
    679     def AllowResizeUpdates(self, resize):
    680         self.noresize = not resize
    681 
    682     def SetMaxLength(self, value):
    683         self.maxletters = value
    684 
    685     def ShowObject(self, obj):
    686         if self.scrollingRange:
    687             absPos = obj.GetAbsolute()
    688             if absPos is not None:
    689                 left, top, width, height = absPos
    690                 cleft, ctop, cwidth, cheight = self.sr.clipper.GetAbsolute()
    691                 top = top - ctop + self._position
    692                 portion = max(top + height - cheight + 5, min(top - 5, self._position)) / float(self.scrollingRange)
    693                 self.ScrollToProportion(portion)
    694 
    695     def GetTextObject_Overwrite(self, text, width = None):
    696         obj = ParserBase.GetTextObject(self, text, width)
    697         fontFlag = obj.fontFlags
    698         fontSize = obj.fontSize
    699         fontColor = obj.color
    700         if fontFlag != self.fontFlag or fontSize != self.fontSize or fontColor != self.fontColor:
    701             if not obj.a:
    702                 obj.color = self.fontColor
    703             obj.fontFlags = self.fontFlag
    704             obj.fontSize = self.fontSize
    705         return obj
    706 
    707     def OnSetFocus(self, *args, **kw):
    708         Scroll.OnSetFocus(self, *args, **kw)
    709         if not self.readonly and uicore.imeHandler:
    710             uicore.imeHandler.SetFocus(self)
    711         self.UpdateNodesCursorIndexes()
    712         self.RefreshCursorAndSelection()
    713         if getattr(self, 'RegisterFocus', None):
    714             self.RegisterFocus(self)
    715 
    716     def OnKillFocus(self, *args, **kw):
    717         Scroll.OnKillFocus(self, *args, **kw)
    718         if not self.readonly and uicore.imeHandler:
    719             uicore.imeHandler.KillFocus(self)
    720         uthread.new(self.RefreshCursorAndSelection)
    721         if getattr(self, 'OnFocusLost', None):
    722             uthread.new(self.OnFocusLost, self)
    723 
    724     def HideCursor(self):
    725         self._showCursor = False
    726         self.RefreshCursorAndSelection()
    727 
    728     def ShowCursor(self):
    729         self._showCursor = False
    730         self.RefreshCursorAndSelection()
    731 
    732     def EvalAttributeState(self):
    733         node, obj, npos = self.GetNodeAndTextObjectFromGlobalCursor()
    734         if obj is not None:
    735             if obj.fontFlags != self.fontFlag or obj.fontSize != self.fontSize or obj.color != self.fontColor or obj.a and obj.a.href != self.href:
    736                 self.fontFlag = obj.fontFlags
    737                 self.fontSize = obj.fontSize
    738                 self.fontColor = obj.color
    739                 self.href = obj.a and obj.a.href
    740                 self._AttribStateChange()
    741 
    742     def GetMenuDelegate(self, node = None):
    743         m = []
    744         if self.sr.window and getattr(self.sr.window, 'GetMenu', None):
    745             m = self.sr.window.GetMenu()
    746         m.append((MenuLabel('/Carbon/UI/Controls/Common/CopyAll'), self.CopyAll))
    747         if self.HasSelection():
    748             m.append((MenuLabel('/Carbon/UI/Controls/Common/CopySelected'), self.Copy))
    749             if not self.readonly:
    750                 m.append((MenuLabel('/Carbon/UI/Controls/Common/CutSelected'), self.Cut))
    751         clipboard = GetClipboardData()
    752         if clipboard and not self.readonly:
    753             m.append((MenuLabel('/Carbon/UI/Controls/Common/Paste'), self.Paste, (clipboard,)))
    754         if not self.readonly:
    755             m.append(None)
    756             linkmenu = [(MenuLabel('/EVE/UI/Common/Character'), self.LinkCharacter),
    757              (MenuLabel('/EVE/UI/Common/Corporation'), self.LinkCorp),
    758              (MenuLabel('/EVE/UI/Common/LocationTypes/SolarSystem'), self.LinkSolarSystem),
    759              (MenuLabel('/EVE/UI/Common/LocationTypes/Station'), self.LinkStation),
    760              (MenuLabel('/Carbon/UI/Controls/Common/ItemType'), self.LinkItemType)]
    761             m.append((MenuLabel('/Carbon/UI/Controls/Common/AutoLink'), linkmenu))
    762         return m
    763 
    764     def SelectLineUnderCursor(self):
    765         node, obj, npos = self.GetNodeAndTextObjectFromGlobalCursor()
    766         if node.startCursorIndex is None:
    767             self.UpdateNodesCursorIndexes()
    768         fromIdx, toIdx = node.startCursorIndex, node.endCursorIndex
    769         self.SetSelectionRange(fromIdx, toIdx)
    770         self.SetSelectionInitPos(fromIdx)
    771         self.SetCursorPos(toIdx)
    772 
    773     def FindWordBoundariesFromGlobalCursor(self, wordShift = 0):
    774         node, obj, npos = self.GetNodeAndTextObjectFromGlobalCursor()
    775         if node.startCursorIndex is None:
    776             self.UpdateNodesCursorIndexes()
    777         text = self.GetSelectedText(getAll=True, newLineStr=' ')
    778         boundaries = []
    779         counter = 0
    780         for each in uiconst.WORD_BOUNDARY_REGEX.split(text):
    781             if each:
    782                 counter += len(each)
    783                 boundaries.append(counter)
    784 
    785         wordStart = 0
    786         wordEnd = None
    787         for boundary in boundaries:
    788             if boundary >= self.globalCursorPos + wordShift:
    789                 wordEnd = boundary
    790                 break
    791             wordStart = boundary
    792 
    793         if wordEnd is None:
    794             wordEnd = len(text)
    795         leftOffset = wordStart - self.globalCursorPos
    796         rightOffset = wordEnd - self.globalCursorPos
    797         return (leftOffset, rightOffset)
    798 
    799     def SelectWordUnderCursor(self):
    800         leftBound, rightBound = self.FindWordBoundariesFromGlobalCursor()
    801         fromIdx, toIdx = self.globalCursorPos + leftBound, self.globalCursorPos + rightBound
    802         self.SetSelectionRange(fromIdx, toIdx)
    803         self.SetSelectionInitPos(fromIdx)
    804         self.SetCursorPos(toIdx)
    805 
    806     def SetSelectionInitPos(self, globalIndex):
    807         self.globalSelectionInitpos = globalIndex
    808 
    809     def CheckCursorAndSelectionUpdate(self):
    810         fromCharIndex, toCharIndex = self.globalSelectionRange
    811         cursorPos = self.globalCursorPos
    812         last_cursorPos, last_fromCharIndex, last_toCharIndex = getattr(self, 'lastCursorUpdateProps', (None, None, None))
    813         if cursorPos != last_cursorPos or fromCharIndex != last_fromCharIndex or toCharIndex != last_toCharIndex:
    814             self.RefreshCursorAndSelection()
    815 
    816     def SetSelectionRange(self, fromCharIndex, toCharIndex):
    817         if fromCharIndex == toCharIndex:
    818             fromCharIndex, toCharIndex = (None, None)
    819         elif toCharIndex < fromCharIndex:
    820             copy_fromCharIndex = fromCharIndex
    821             fromCharIndex = toCharIndex
    822             toCharIndex = copy_fromCharIndex
    823         self.globalSelectionRange = (fromCharIndex, toCharIndex)
    824         self.CheckCursorAndSelectionUpdate()
    825 
    826     def SetCursorPos(self, globalIndex, forceUpdate = False):
    827         if globalIndex == -1:
    828             globalIndex = self._maxGlobalCursorIndex
    829         self.globalCursorPos = max(0, min(self._maxGlobalCursorIndex, globalIndex))
    830         if forceUpdate:
    831             self.RefreshCursorAndSelection()
    832         else:
    833             self.CheckCursorAndSelectionUpdate()
    834 
    835     def RefreshCursorAndSelection(self):
    836         if self.destroyed:
    837             return
    838         self.UpdateNodesCursorIndexes()
    839         fromIdx, toIdx = self.globalSelectionRange
    840         for node in self.GetNodes():
    841             if not issubclass(node.decoClass, SE_TextlineCore):
    842                 continue
    843             if node.startCursorIndex is None:
    844                 self.UpdateNodesCursorIndexes()
    845             if node.startCursorIndex <= self.globalCursorPos <= node.endCursorIndex:
    846                 indexInLine = self.globalCursorPos - node.startCursorIndex
    847                 startCheck = node.startCursorIndex - node.pos
    848                 for obj in node.stack:
    849                     lenLetters = len(obj.letters or '')
    850                     if startCheck <= self.globalCursorPos <= startCheck + lenLetters:
    851                         self._activeNodeObjPos = (node, obj, node.startCursorIndex - startCheck + indexInLine)
    852                         break
    853                     startCheck += lenLetters
    854 
    855             if not self.readonly:
    856                 if node.cursorPos is not None:
    857                     node.cursorPos = None
    858                     if node.panel:
    859                         node.panel.UpdateCursor()
    860             if fromIdx is None or not (fromIdx <= node.startCursorIndex <= toIdx or node.startCursorIndex <= fromIdx <= node.endCursorIndex):
    861                 node.selectionStartIndex = None
    862                 node.selectionEndIndex = None
    863             else:
    864                 node.selectionStartIndex = max(0, fromIdx - node.startCursorIndex)
    865                 node.selectionEndIndex = max(0, min(node.letterCountInLine, toIdx - node.startCursorIndex))
    866             if node.panel and hasattr(node.panel, 'UpdateSelectionHilite'):
    867                 node.panel.UpdateSelectionHilite()
    868 
    869         if not self.readonly:
    870             node, obj, npos = self.GetNodeAndTextObjectFromGlobalCursor()
    871             if node:
    872                 node.cursorPos = self.globalCursorPos - node.startCursorIndex
    873                 if node.panel:
    874                     node.panel.UpdateCursor()
    875         self.lastCursorUpdateProps = (self.globalCursorPos, fromIdx, toIdx)
    876 
    877     def GetNodeAndTextObjectFromGlobalCursor(self):
    878         return getattr(self, '_activeNodeObjPos', (None, None, 0))
    879 
    880     def GetSelectedText(self, getAll = False, newLineStr = '\r\n'):
    881         if getAll:
    882             ret = ''
    883             for stackIndex, (stack, attrs) in enumerate(self.textbuffers):
    884                 for obj in stack:
    885                     if obj.type == '<text>':
    886                         ret += obj.letters
    887                     elif obj.type == '<table>' and obj.control:
    888                         tableValue = obj.control.GetValue()
    889                         if tableValue:
    890                             ret += tableValue + newLineStr
    891 
    892                 ret += newLineStr
    893 
    894             return ret
    895         fromIdx, toIdx = self.globalSelectionRange
    896         if fromIdx == toIdx:
    897             return ''
    898         newret = ''
    899         for node in self.GetNodes():
    900             if not issubclass(node.decoClass, SE_TextlineCore):
    901                 continue
    902             if node.selectionStartIndex is None:
    903                 continue
    904             if node.inlines:
    905                 for inline, x in node.inlines:
    906                     if inline.type == '<table>' and inline.control:
    907                         tableText = inline.control.GetValue()
    908                         if tableText:
    909                             newret += tableText + newLineStr
    910 
    911             if node.glyphString is None:
    912                 continue
    913             if newret and node.pos == 0:
    914                 newret += newLineStr
    915             text = ''.join([ glyphData[4] for glyphData in node.glyphString if glyphData[4] is not None ])
    916             if node.startCursorIndex <= fromIdx <= node.startCursorIndex + len(text):
    917                 newret += text[fromIdx - node.startCursorIndex:toIdx - node.startCursorIndex]
    918             elif node.startCursorIndex <= toIdx <= node.startCursorIndex + len(text):
    919                 newret += text[:toIdx - node.startCursorIndex]
    920             else:
    921                 newret += text
    922 
    923         return newret
    924 
    925     def LinkCharacter(self):
    926         if self.GetSelection() == 0:
    927             self.SelectWordUnderCursor()
    928         txt = self.GetSelectedText()
    929         if txt:
    930             txt = txt.strip()
    931         self.ApplySelection(6, data={'text': txt,
    932          'link': 'char'})
    933 
    934     def LinkCorp(self):
    935         if self.GetSelection() == 0:
    936             self.SelectWordUnderCursor()
    937         txt = self.GetSelectedText()
    938         if txt:
    939             txt = txt.strip()
    940         self.ApplySelection(6, data={'text': txt,
    941          'link': 'corp'})
    942 
    943     def LinkSolarSystem(self):
    944         if self.GetSelection() == 0:
    945             self.SelectWordUnderCursor()
    946         txt = self.GetSelectedText()
    947         if txt:
    948             txt = txt.strip()
    949         self.ApplySelection(6, data={'text': txt,
    950          'link': 'solarsystem'})
    951 
    952     def LinkStation(self):
    953         if self.GetSelection() == 0:
    954             self.SelectWordUnderCursor()
    955         txt = self.GetSelectedText()
    956         if txt:
    957             txt = txt.strip()
    958         self.ApplySelection(6, data={'text': txt,
    959          'link': 'station'})
    960 
    961     def LinkItemType(self):
    962         if self.GetSelection() == 0:
    963             self.SelectWordUnderCursor()
    964         txt = self.GetSelectedText()
    965         if txt:
    966             txt = txt.strip()
    967         self.ApplySelection(6, data={'text': txt,
    968          'link': 'type'})
    969 
    970     def OnMouseUpDelegate(self, _node, *args):
    971         self._selecting = 0
    972         self.sr.scrollTimer = None
    973 
    974     def OnMouseDownDelegate(self, _node, *args):
    975         self.UpdateNodesCursorIndexes()
    976         self._selecting = 1
    977         self.sr.scrollTimer = AutoTimer(100, self.ScrollTimer)
    978         if _node and _node.panel and _node.panel.children:
    979             self.SetCursorFromNodeAndMousePos(_node)
    980             if uicore.uilib.Key(uiconst.VK_SHIFT) and self.globalSelectionInitpos is not None:
    981                 fromIdx, toIdx = self.globalSelectionRange
    982                 newIndex = self.globalCursorPos
    983                 toIndex = self.globalSelectionInitpos
    984                 if newIndex > toIndex:
    985                     self.SetSelectionRange(toIndex, newIndex)
    986                 else:
    987                     self.SetSelectionRange(newIndex, toIndex)
    988             else:
    989                 self.SetSelectionRange(None, None)
    990                 self.SetSelectionInitPos(self.globalCursorPos)
    991             self.EvalAttributeState()
    992 
    993     def OnMouseMoveDelegate(self, *args):
    994         if not self._selecting or self.globalSelectionInitpos is None:
    995             return
    996         if not uicore.uilib.leftbtn:
    997             self._selecting = 0
    998             return
    999         toAffect = self.CrawlForTextline(uicore.uilib.mouseOver)
   1000         if toAffect is None:
   1001             toAffect = self.GetLastTextline()
   1002             if toAffect is None:
   1003                 return
   1004         node = toAffect.sr.node
   1005         if node is None:
   1006             return
   1007         self.SetCursorFromNodeAndMousePos(node)
   1008         if self.globalCursorPos > self.globalSelectionInitpos:
   1009             self.SetSelectionRange(self.globalSelectionInitpos, self.globalCursorPos)
   1010         else:
   1011             self.SetSelectionRange(self.globalCursorPos, self.globalSelectionInitpos)
   1012         self.EvalAttributeState()
   1013 
   1014     def SetCursorFromNodeAndMousePos(self, node):
   1015         if node.startCursorIndex is None:
   1016             self.UpdateNodesCursorIndexes()
   1017         if node.panel is not None:
   1018             toCursorPos = uicore.uilib.x - node.panel.sr.inlines.GetAbsolute()[0]
   1019             internalPos = uicore.font.GetLetterIdx(node, uicore.ScaleDpi(toCursorPos))[0]
   1020             startCursorIndex = node.startCursorIndex or 0
   1021             self.SetCursorPos(startCursorIndex + internalPos)
   1022         else:
   1023             self.SetCursorPos(-1)
   1024 
   1025     def SetCursorPosAtObjectEnd(self, toEnd):
   1026         for node in self.GetNodes():
   1027             if not issubclass(node.decoClass, SE_TextlineCore):
   1028                 continue
   1029             if node.startCursorIndex is None:
   1030                 self.UpdateNodesCursorIndexes()
   1031             counter = 0
   1032             for obj in node.stack:
   1033                 if obj.letters:
   1034                     counter += len(obj.letters)
   1035                 if obj is toEnd:
   1036                     self.SetCursorPos(node.startCursorIndex + counter)
   1037                     return
   1038 
   1039     def GetLastTextline(self):
   1040         totalLines = len(self.sr.content.children)
   1041         for i in xrange(totalLines):
   1042             nodePanel = self.sr.content.children[totalLines - i - 1]
   1043             if isinstance(nodePanel, SE_TextlineCore):
   1044                 return nodePanel
   1045 
   1046     def CrawlForTextline(self, mo):
   1047         if isinstance(mo, SE_TextlineCore):
   1048             return mo
   1049         if mo.parent:
   1050             if mo.parent is uicore.desktop:
   1051                 return None
   1052             return self.CrawlForTextline(mo.parent)
   1053 
   1054     def ScrollTimer(self):
   1055         if uicore.uilib.leftbtn and self._selecting:
   1056             aL, aT, aW, aH = self.GetAbsolute()
   1057             if uicore.uilib.y < aT:
   1058                 uthread.new(self.Scroll, 1)
   1059             elif uicore.uilib.y > aT + aH:
   1060                 uthread.new(self.Scroll, -1)
   1061         else:
   1062             self.sr.scrollTimer = None
   1063 
   1064     def OnDragEnterDelegate(self, node, nodes):
   1065         if self.readonly:
   1066             return
   1067         self.SetCursorFromNodeAndMousePos(node)
   1068 
   1069     def OnDragMoveDelegate(self, node, nodes):
   1070         if self.readonly:
   1071             return
   1072         self.SetCursorFromNodeAndMousePos(node)
   1073 
   1074     def OnDropDataDelegate(self, node, nodes):
   1075         if self.readonly:
   1076             return
   1077         self.SetCursorFromNodeAndMousePos(node)
   1078 
   1079     def OnContentDropData(self, dragObj, nodes):
   1080         self.SetCursorPos(self._maxGlobalCursorIndex)
   1081         node, obj, npos = self.GetNodeAndTextObjectFromGlobalCursor()
   1082         self.OnDropDataDelegate(node, nodes)
   1083 
   1084     def OnDropData(self, dragObj, nodes):
   1085         node, obj, npos = self.GetNodeAndTextObjectFromGlobalCursor()
   1086         self.OnDropDataDelegate(node, nodes)
   1087 
   1088     def OnMouseUp(self, *args):
   1089         self._selecting = 0
   1090         self.sr.scrollTimer = None
   1091 
   1092     def OnMouseDown(self, button, *args):
   1093         if button != uiconst.MOUSELEFT:
   1094             return
   1095         if len(self.sr.content.children):
   1096             self.UpdateNodesCursorIndexes()
   1097             shift = uicore.uilib.Key(uiconst.VK_SHIFT)
   1098             lastEntry = self.sr.content.children[-1]
   1099             l, t, w, h = lastEntry.GetAbsolute()
   1100             if uicore.uilib.y > t + h:
   1101                 if shift:
   1102                     selectionStartIndex, selectionEndIndex = self.globalSelectionRange
   1103                     self.SetSelectionRange(selectionStartIndex or self.globalCursorPos, self._maxGlobalCursorIndex)
   1104                 else:
   1105                     self.SetSelectionRange(None, None)
   1106                     self.SetSelectionInitPos(self._maxGlobalCursorIndex)
   1107                 self.SetCursorPos(self._maxGlobalCursorIndex)
   1108         self._selecting = 1
   1109 
   1110     def OnMouseMove(self, *args):
   1111         self.OnMouseMoveDelegate()
   1112 
   1113     def OnKeyDown(self, vkey, flag, *args, **kw):
   1114         isMac = sys.platform.startswith('darwin')
   1115 
   1116         def k(win, mac):
   1117             if isMac:
   1118                 return mac
   1119             return win
   1120 
   1121         ctrl = uicore.uilib.Key(uiconst.VK_CONTROL)
   1122         alt = uicore.uilib.Key(uiconst.VK_MENU)
   1123         cmd = uicore.uilib.Key(uiconst.VK_LWIN)
   1124         if vkey in (uiconst.VK_LEFT,
   1125          uiconst.VK_RIGHT,
   1126          uiconst.VK_UP,
   1127          uiconst.VK_DOWN,
   1128          uiconst.VK_HOME,
   1129          uiconst.VK_END):
   1130             if self.globalCursorPos is None:
   1131                 return
   1132             shift = uicore.uilib.Key(uiconst.VK_SHIFT)
   1133             if not shift:
   1134                 self.SetSelectionRange(None, None)
   1135             newCursorPos = self.globalCursorPos
   1136             node, obj, npos = self.GetNodeAndTextObjectFromGlobalCursor()
   1137             if node.startCursorIndex is None:
   1138                 self.UpdateNodesCursorIndexes()
   1139             if vkey == uiconst.VK_LEFT and (not isMac or not cmd):
   1140                 if k(win=ctrl, mac=alt):
   1141                     leftBound, rightBound = self.FindWordBoundariesFromGlobalCursor()
   1142                     newCursorPos = self.globalCursorPos + leftBound
   1143                 else:
   1144                     newCursorPos = max(0, self.globalCursorPos - 1)
   1145                 if shift:
   1146                     selectionStartIndex, selectionEndIndex = self.globalSelectionRange
   1147                     if selectionStartIndex is None:
   1148                         self.SetSelectionRange(newCursorPos, self.globalCursorPos)
   1149                     elif self.globalCursorPos == selectionStartIndex:
   1150                         self.SetSelectionRange(newCursorPos, selectionEndIndex)
   1151                     else:
   1152                         self.SetSelectionRange(selectionStartIndex, newCursorPos)
   1153             elif vkey == uiconst.VK_RIGHT and (not isMac or not cmd):
   1154                 if k(win=ctrl, mac=alt):
   1155                     leftBound, rightBound = self.FindWordBoundariesFromGlobalCursor(wordShift=1)
   1156                     newCursorPos = self.globalCursorPos + rightBound
   1157                 else:
   1158                     newCursorPos = self.globalCursorPos + 1
   1159                 if shift:
   1160                     selectionStartIndex, selectionEndIndex = self.globalSelectionRange
   1161                     if selectionStartIndex is None:
   1162                         self.SetSelectionRange(self.globalCursorPos, newCursorPos)
   1163                     elif self.globalCursorPos == selectionStartIndex:
   1164                         self.SetSelectionRange(newCursorPos, selectionEndIndex)
   1165                     else:
   1166                         self.SetSelectionRange(selectionStartIndex, newCursorPos)
   1167             elif vkey == uiconst.VK_UP:
   1168                 if not k(win=ctrl, mac=cmd):
   1169                     self.OnUp()
   1170                     if self.globalSelectionRange == (None, None):
   1171                         self.Scroll(1 + 10 * shift)
   1172                     else:
   1173                         posInLine = self.globalCursorPos - node.startCursorIndex
   1174                         if node.idx > 0:
   1175                             aboveIdx = node.idx - 1
   1176                             nodeAbove = None
   1177                             while aboveIdx:
   1178                                 nodeAbove = self.GetNode(aboveIdx)
   1179                                 if nodeAbove and nodeAbove.startCursorIndex != nodeAbove.endCursorIndex:
   1180                                     break
   1181                                 aboveIdx -= 1
   1182 
   1183                             if nodeAbove and nodeAbove.endCursorIndex is not None:
   1184                                 newCursorPos = min(nodeAbove.endCursorIndex, nodeAbove.startCursorIndex + posInLine)
   1185                             else:
   1186                                 newCursorPos = 0
   1187                             if shift:
   1188                                 selectionStartIndex, selectionEndIndex = self.globalSelectionRange
   1189                                 if selectionStartIndex is None:
   1190                                     self.SetSelectionRange(newCursorPos, self.globalCursorPos)
   1191                                 elif self.globalCursorPos == selectionStartIndex:
   1192                                     self.SetSelectionRange(newCursorPos, selectionEndIndex)
   1193                                 else:
   1194                                     self.SetSelectionRange(selectionStartIndex, newCursorPos)
   1195             elif vkey == uiconst.VK_DOWN:
   1196                 if not k(win=ctrl, mac=cmd):
   1197                     self.OnDown()
   1198                     if self.globalSelectionRange == (None, None):
   1199                         self.Scroll(-1 - 10 * shift)
   1200                     else:
   1201                         posInLine = self.globalCursorPos - node.startCursorIndex
   1202                         nodeBelow = self.GetNode(node.idx + 1)
   1203                         if nodeBelow and nodeBelow.startCursorIndex is not None:
   1204                             newCursorPos = nodeBelow.startCursorIndex + min(posInLine, nodeBelow.letterCountInLine)
   1205                         else:
   1206                             newCursorPos = self._maxGlobalCursorIndex
   1207                     if shift:
   1208                         selectionStartIndex, selectionEndIndex = self.globalSelectionRange
   1209                         if selectionStartIndex is None:
   1210                             self.SetSelectionRange(self.globalCursorPos, newCursorPos)
   1211                         elif self.globalCursorPos == selectionStartIndex:
   1212                             self.SetSelectionRange(newCursorPos, selectionEndIndex)
   1213                         else:
   1214                             self.SetSelectionRange(selectionStartIndex, newCursorPos)
   1215             elif vkey == uiconst.VK_HOME or vkey == uiconst.VK_LEFT and isMac and cmd:
   1216                 self.OnHome()
   1217                 if vkey == uiconst.VK_HOME and ctrl:
   1218                     newCursorPos = 0
   1219                 else:
   1220                     newCursorPos = node.startCursorIndex
   1221                 if shift:
   1222                     selectionStartIndex, selectionEndIndex = self.globalSelectionRange
   1223                     if selectionStartIndex is None:
   1224                         self.SetSelectionRange(newCursorPos, self.globalCursorPos)
   1225                     elif self.globalCursorPos == selectionStartIndex:
   1226                         self.SetSelectionRange(newCursorPos, selectionEndIndex)
   1227                     else:
   1228                         self.SetSelectionRange(newCursorPos, selectionEndIndex)
   1229             elif vkey == uiconst.VK_END or vkey == uiconst.VK_RIGHT and isMac and cmd:
   1230                 self.OnEnd()
   1231                 if vkey == uiconst.VK_END and ctrl:
   1232                     newCursorPos = self._maxGlobalCursorIndex
   1233                 else:
   1234                     newCursorPos = node.endCursorIndex
   1235                 if shift:
   1236                     selectionStartIndex, selectionEndIndex = self.globalSelectionRange
   1237                     if selectionStartIndex is None:
   1238                         self.SetSelectionRange(self.globalCursorPos, newCursorPos)
   1239                     elif self.globalCursorPos == selectionStartIndex:
   1240                         self.SetSelectionRange(selectionStartIndex, newCursorPos)
   1241                     else:
   1242                         self.SetSelectionRange(selectionStartIndex, newCursorPos)
   1243             self.SetCursorPos(newCursorPos)
   1244             self.EvalAttributeState()
   1245             if self.globalSelectionRange != (None, None):
   1246                 node, obj, npos = self.GetNodeAndTextObjectFromGlobalCursor()
   1247                 if node:
   1248                     self.ShowNodeIdx(node.idx)
   1249         if self.readonly:
   1250             return
   1251         if k(win=ctrl, mac=cmd):
   1252             if vkey == uiconst.VK_B:
   1253                 self.ToggleBold()
   1254             if vkey == uiconst.VK_U:
   1255                 self.ToggleUnderline()
   1256             if vkey == uiconst.VK_I:
   1257                 self.ToggleItalic()
   1258             if vkey == uiconst.VK_ADD:
   1259                 self.EnlargeSize()
   1260             if vkey == uiconst.VK_SUBTRACT:
   1261                 self.ReduceSize()
   1262             if vkey == uiconst.VK_UP:
   1263                 self.CtrlUp(self)
   1264             if vkey == uiconst.VK_DOWN:
   1265                 self.CtrlDown(self)
   1266             return
   1267         if vkey == uiconst.VK_DELETE:
   1268             self.OnChar(127, flag)
   1269 
   1270     def OnChar(self, char, flag):
   1271         if self.readonly:
   1272             return False
   1273         if char < 32 and char not in (uiconst.VK_RETURN, uiconst.VK_BACK):
   1274             return False
   1275         if self.globalCursorPos is None:
   1276             return False
   1277         self.keybuffer.append(char)
   1278         if not self.updating:
   1279             if char == uiconst.VK_RETURN and not uicore.uilib.Key(uiconst.VK_SHIFT) and self.OnReturn:
   1280                 self.Simplify()
   1281                 return uthread.new(self.OnReturn)
   1282             try:
   1283                 self.updating = 1
   1284                 while len(self.keybuffer):
   1285                     char = self.keybuffer.pop(0)
   1286                     if self.DeleteSelected() and char in [127, uiconst.VK_BACK]:
   1287                         continue
   1288                     self.Insert(char)
   1289 
   1290             finally:
   1291                 self.updating = 0
   1292 
   1293         return True
   1294 
   1295     def SelectAll(self, *args):
   1296         self.SetSelectionRange(0, self._maxGlobalCursorIndex)
   1297 
   1298     def Copy(self):
   1299         selstring = self.GetSelectedText()
   1300         if selstring:
   1301             blue.pyos.SetClipboardData(selstring)
   1302 
   1303     def CopyAll(self):
   1304         all = self.GetSelectedText(getAll=True)
   1305         if all:
   1306             blue.pyos.SetClipboardData(all)
   1307 
   1308     def Cut(self):
   1309         self.Copy()
   1310         if not self.readonly:
   1311             self.DeleteSelected()
   1312 
   1313     def EnoughRoomFor(self, textLen):
   1314         if not self.maxletters:
   1315             return True
   1316         currentLen = len(StripTags(self.GetSelectedText(getAll=True)))
   1317         if currentLen + textLen <= self.maxletters:
   1318             return True
   1319         return textLen <= self.RoomLeft()
   1320 
   1321     def RoomLeft(self):
   1322         if not self.maxletters:
   1323             return None
   1324         currentLen = len(self.GetValue(0))
   1325         return self.maxletters + len(self.GetSelectedText()) - currentLen
   1326 
   1327     def Paste(self, text):
   1328         roomLeft = self.RoomLeft()
   1329         if roomLeft is not None and roomLeft < len(text):
   1330             prompt_player_fsd_dialog('uiwarning03')
   1331             text = text[:roomLeft]
   1332         if self.readonly or not text:
   1333             return
   1334         self.DeleteSelected(reloadAll=False)
   1335         currentCursor = self.globalCursorPos
   1336         text = text.replace('\t', '    ').replace('\r', '')
   1337         lines = text.split('\n')
   1338         line = lines.pop(0)
   1339         node, obj, npos = self.GetNodeAndTextObjectFromGlobalCursor()
   1340         endObj = obj.Copy()
   1341         endObj.letters = obj.letters[npos:]
   1342         lastStack = node.stack
   1343         obj.letters = obj.letters[:npos] + line
   1344         if lines:
   1345             stackIndex = 0
   1346             attrStack = None
   1347             for s, a in self.textbuffers:
   1348                 if s is node.stack:
   1349                     attrStack = a
   1350                     break
   1351                 stackIndex += 1
   1352             else:
   1353                 attrStack = self.attrStack[-1]
   1354 
   1355             for lineIdx, line in enumerate(lines):
   1356                 betweenObj = obj.Copy()
   1357                 betweenObj.letters = line
   1358                 newStack = [betweenObj]
   1359                 self.textbuffers.insert(stackIndex + lineIdx + 1, (newStack, attrStack))
   1360                 lastStack = newStack
   1361 
   1362         if endObj.letters:
   1363             lastStack.append(endObj)
   1364         self.UpdateNodesCursorIndexes()
   1365         self.DoContentResize()
   1366         self.SetCursorPos(currentCursor + len(text))
   1367         node, obj, npos = self.GetNodeAndTextObjectFromGlobalCursor()
   1368         if node:
   1369             self.ShowNodeIdx(node.idx)
   1370 
   1371     def Insert(self, char):
   1372         if char not in (uiconst.VK_BACK, uiconst.VK_RETURN, 127) and not self.EnoughRoomFor(1):
   1373             prompt_player_fsd_dialog('uiwarning03')
   1374             return
   1375         self.Simplify()
   1376         node, obj, npos = self.GetNodeAndTextObjectFromGlobalCursor()
   1377         stackIdx = 0
   1378         prevStack = None
   1379         for s, a in self.textbuffers:
   1380             if s is node.stack:
   1381                 attrs = a
   1382                 break
   1383             prevStack = s
   1384             stackIdx += 1
   1385         else:
   1386             attrs = self.attrStack[-1]
   1387 
   1388         cursorAdvance = 0
   1389         if char == uiconst.VK_RETURN:
   1390             if obj.a and npos == len(obj.letters):
   1391                 newobj = obj.Copy()
   1392                 newobj.letters = ''
   1393                 newobj.a = None
   1394                 node.stack.insert(node.stack.index(obj) + 1, newobj)
   1395                 self.href = None
   1396                 self.fontFlag &= ~(fontflags.b | fontflags.u)
   1397                 self.fontColor = (1.0, 1.0, 1.0, 0.75)
   1398                 newobj.fontFlags = self.fontFlag
   1399                 newobj.color = self.fontColor
   1400                 obj = newobj
   1401                 npos = 0
   1402             cursorAdvance = 1
   1403             newobj = obj.Copy()
   1404             newobj.letters = obj.letters[npos:]
   1405             obj.letters = obj.letters[:npos]
   1406             newstack = [newobj]
   1407             startMove = False
   1408             for textobject in node.stack[:]:
   1409                 if startMove:
   1410                     newstack.append(textobject)
   1411                     node.stack.remove(textobject)
   1412                     continue
   1413                 if textobject is obj:
   1414                     startMove = True
   1415 
   1416             sIdx = 0
   1417             for sIdx, (thisStack, thisAttrs) in enumerate(self.textbuffers):
   1418                 if thisStack is node.stack:
   1419                     break
   1420 
   1421             self.textbuffers.insert(sIdx + 1, (newstack, attrs))
   1422             self.RefreshLine(node, attrs, fullRefresh=True)
   1423             self.InsertLines(newstack, 0, node.idx + 1, attrs)
   1424         elif char == 127:
   1425             if npos == len(obj.letters):
   1426                 if obj is node.stack[-1]:
   1427                     if stackIdx + 1 < len(self.textbuffers):
   1428                         nextStack, nextAttrs = self.textbuffers[stackIdx + 1]
   1429                         del self.textbuffers[stackIdx + 1]
   1430                         node.stack += nextStack
   1431                         self.RemoveStack(nextStack)
   1432                         self.UpdateNodes(nextStack, node.stack)
   1433                         if len(obj.letters) == 0:
   1434                             node.stack.remove(obj)
   1435                     else:
   1436                         return
   1437                 else:
   1438                     obj = node.stack[node.stack.index(obj) + 1]
   1439                     obj.letters = obj.letters[1:]
   1440             else:
   1441                 obj.letters = obj.letters[:npos] + obj.letters[npos + 1:]
   1442             self.RefreshLine(node, attrs)
   1443         elif char == uiconst.VK_BACK:
   1444             if npos == 0:
   1445                 if self.globalCursorPos != 0 and prevStack:
   1446                     prevStack += node.stack
   1447                     oldStack = node.stack
   1448                     self.RemoveStack(oldStack)
   1449                     self.UpdateNodes(node.stack, prevStack)
   1450                     if len(obj.letters) == 0:
   1451                         node.stack.remove(obj)
   1452                     self.RefreshLine(node, attrs)
   1453                     self.Simplify()
   1454                     cursorAdvance = -1
   1455             else:
   1456                 obj.letters = obj.letters[:npos - 1] + obj.letters[npos:]
   1457                 self.RefreshLine(node, attrs)
   1458                 cursorAdvance = -1
   1459         else:
   1460             if char == uiconst.VK_SPACE and obj.a and npos == len(obj.letters):
   1461                 newobj = obj.Copy()
   1462                 newobj.letters = ' '
   1463                 newobj.a = None
   1464                 node.stack.insert(node.stack.index(obj) + 1, newobj)
   1465                 self.href = None
   1466                 self.fontFlag &= ~(fontflags.b | fontflags.u)
   1467                 self.fontColor = (1.0, 1.0, 1.0, 0.75)
   1468                 newobj.fontFlags = self.fontFlag
   1469                 newobj.color = self.fontColor
   1470             else:
   1471                 char = unichr(char)
   1472                 newobj, npos = self.CheckAttribs(node.stack, obj, npos)
   1473                 if newobj is not obj:
   1474                     newobj.letters = char
   1475                 else:
   1476                     obj.letters = obj.letters[:npos] + char + obj.letters[npos:]
   1477             cursorAdvance = 1
   1478             self.RefreshLine(node, attrs)
   1479         if hasattr(self, 'OnChange'):
   1480             self.OnChange()
   1481         self.UpdateNodesCursorIndexes()
   1482         self.SetCursorPos(self.globalCursorPos + cursorAdvance, forceUpdate=True)
   1483         self.EvalAttributeState()
   1484         node, obj, npos = self.GetNodeAndTextObjectFromGlobalCursor()
   1485         if node:
   1486             self.ShowNodeIdx(node.idx)
   1487 
   1488     def RemoveStack(self, stack):
   1489         idx = 0
   1490         delIdx = None
   1491         for s, a in self.textbuffers:
   1492             if stack is s:
   1493                 delIdx = idx
   1494                 break
   1495             idx += 1
   1496 
   1497         if delIdx is not None:
   1498             del self.textbuffers[delIdx]
   1499 
   1500     def UpdateNodesCursorIndexes(self):
   1501         i = 0
   1502         stackShift = 0
   1503         lastStack = None
   1504         globalCursorIndex = 0
   1505         for node in self.GetNodes():
   1506             if not issubclass(node.decoClass, SE_TextlineCore):
   1507                 continue
   1508             if node.glyphString:
   1509                 letterCountInLine = len(node.glyphString)
   1510             else:
   1511                 letterCountInLine = 0
   1512             node.letterCountInLine = letterCountInLine
   1513             if lastStack and lastStack is not node.stack:
   1514                 stackShift += 1
   1515             node.startCursorIndex = globalCursorIndex + stackShift
   1516             node.endCursorIndex = node.startCursorIndex + letterCountInLine
   1517             globalCursorIndex += letterCountInLine
   1518             lastStack = node.stack
   1519             i += 1
   1520 
   1521         self._maxGlobalCursorIndex = max(0, globalCursorIndex + stackShift)
   1522 
   1523     def CheckAttribs(self, stack, obj, npos):
   1524         fontFlag = obj.fontFlags
   1525         fontSize = obj.fontSize
   1526         fontColor = obj.color
   1527         if fontFlag != self.fontFlag or fontSize != self.fontSize or fontColor != self.fontColor:
   1528             obj, npos = self.SplitObj(stack, obj, npos)
   1529             obj.color = self.fontColor
   1530             obj.fontFlags = self.fontFlag
   1531             obj.fontSize = self.fontSize
   1532             if not self.href:
   1533                 obj.a = None
   1534         return (obj, npos)
   1535 
   1536     def SplitObj(self, stack, obj, npos):
   1537         if not len(obj.letters):
   1538             return (obj, 0)
   1539         newobj = obj.Copy()
   1540         newobj.letters = ''
   1541         if npos == 0:
   1542             stack.insert(stack.index(obj), newobj)
   1543         elif npos == len(obj.letters):
   1544             stack.insert(stack.index(obj) + 1, newobj)
   1545         else:
   1546             stack.insert(stack.index(obj), newobj)
   1547             newobj2 = obj.Copy()
   1548             newobj2.letters = obj.letters[:npos]
   1549             stack.insert(stack.index(obj) - 1, newobj2)
   1550             obj.letters = obj.letters[npos:]
   1551         return (newobj, 0)
   1552 
   1553     def BreakObject(self, obj, npos):
   1554         stack = None
   1555         stackIndex = 0
   1556         for s, a in self.textbuffers:
   1557             for _obj in s:
   1558                 if _obj is obj:
   1559                     stack = s
   1560                     attrs = a
   1561                     break
   1562 
   1563             stackIndex += 1
   1564 
   1565         newobj = obj.Copy()
   1566         newobj.letters = newobj.letters[npos:]
   1567         obj.letters = obj.letters[:npos]
   1568         newStack = [newobj]
   1569         rm = []
   1570         found = False
   1571         for other in stack:
   1572             if found:
   1573                 newStack.append(other)
   1574                 rm.append(other)
   1575             if other is obj:
   1576                 found = True
   1577 
   1578         for each in rm:
   1579             stack.remove(each)
   1580 
   1581         self.textbuffers.insert(stackIndex + 1, (newStack, attrs))
   1582         return newobj
   1583 
   1584     def GetSelection(self):
   1585         return self.HasSelection()
   1586 
   1587     def HasSelection(self):
   1588         fromIdx, toIdx = self.globalSelectionRange
   1589         return fromIdx != toIdx
   1590 
   1591     def DeleteSelected(self, reloadAll = True):
   1592         fromIdx, toIdx = self.globalSelectionRange
   1593         if fromIdx == toIdx:
   1594             return
   1595         self.Simplify()
   1596         delRange = abs(fromIdx - toIdx)
   1597         counter = 0
   1598         doneDeleting = False
   1599         startObj = None
   1600         endObj = None
   1601         clearStacks = []
   1602         for stackIndex, (stack, attrs) in enumerate(self.textbuffers):
   1603             for obj in stack:
   1604                 if obj.type == '<text>':
   1605                     lettersAmount = len(obj.letters)
   1606                     if counter <= fromIdx <= counter + lettersAmount:
   1607                         obj.letters = obj.letters[:fromIdx - counter] + obj.letters[fromIdx - counter + delRange:]
   1608                         startObj = (stack, obj)
   1609                     elif counter <= toIdx <= counter + lettersAmount:
   1610                         obj.letters = obj.letters[toIdx - counter:]
   1611                         endObj = (stack, obj)
   1612                     elif fromIdx <= counter <= toIdx and fromIdx <= counter + lettersAmount <= toIdx:
   1613                         obj.letters = ''
   1614                         clearStacks.append(stackIndex)
   1615                     counter += lettersAmount
   1616                     if counter > toIdx:
   1617                         doneDeleting = True
   1618                         break
   1619 
   1620             counter += 1
   1621             if doneDeleting:
   1622                 break
   1623 
   1624         clearStacks.reverse()
   1625         for stackIndex in clearStacks:
   1626             if len(self.textbuffers) > stackIndex:
   1627                 del self.textbuffers[stackIndex]
   1628 
   1629         if startObj and endObj and startObj[0] is not endObj[0]:
   1630             startObj[0].append(endObj[1])
   1631             endObj[0].remove(endObj[1])
   1632         self.Simplify()
   1633         if reloadAll:
   1634             self.DoContentResize()
   1635         self.UpdateNodesCursorIndexes()
   1636         self.SetCursorPos(fromIdx)
   1637         self.SetSelectionRange(None, None)
   1638         return 1
   1639 
   1640     def ApplySelection(self, what, data = None):
   1641         fromIdx, toIdx = self.globalSelectionRange
   1642         if fromIdx == toIdx:
   1643             return
   1644         self.Simplify()
   1645         selectionRange = abs(fromIdx - toIdx)
   1646         counter = 0
   1647         startObjData = None
   1648         endObjData = None
   1649         startAndEndObjData = None
   1650         changeObjs = []
   1651         for stackIndex, (stack, attrs) in enumerate(self.textbuffers):
   1652             for obj in stack:
   1653                 if obj.type == '<text>':
   1654                     lettersAmount = len(obj.letters)
   1655                     if counter <= fromIdx <= counter + lettersAmount:
   1656                         if counter <= toIdx <= counter + lettersAmount:
   1657                             startAndEndObjData = (stack, obj, fromIdx - counter)
   1658                             break
   1659                         else:
   1660                             startObjData = (stack, obj, fromIdx - counter)
   1661                     elif counter <= toIdx <= counter + lettersAmount:
   1662                         if not endObjData:
   1663                             endObjData = (stack, obj, toIdx - counter)
   1664                     elif fromIdx <= counter and counter + lettersAmount <= toIdx:
   1665                         changeObjs.append(obj)
   1666                     counter += lettersAmount
   1667 
   1668             counter += 1
   1669             if endObjData or startAndEndObjData:
   1670                 break
   1671 
   1672         if startAndEndObjData:
   1673             stack, obj, npos = startAndEndObjData
   1674             newObj = obj.Copy()
   1675             newObj.letters = obj.letters[npos:npos + selectionRange]
   1676             stack.insert(stack.index(obj) + 1, newObj)
   1677             changeObjs.insert(0, newObj)
   1678             newObj2 = obj.Copy()
   1679             newObj2.letters = obj.letters[npos + selectionRange:]
   1680             stack.insert(stack.index(newObj) + 1, newObj2)
   1681             obj.letters = obj.letters[:npos]
   1682         if startObjData:
   1683             stack, obj, npos = startObjData
   1684             newObj = obj.Copy()
   1685             newObj.letters = obj.letters[npos:]
   1686             obj.letters = obj.letters[:npos]
   1687             stack.insert(stack.index(obj) + 1, newObj)
   1688             changeObjs.insert(0, newObj)
   1689         if endObjData:
   1690             stack, obj, npos = endObjData
   1691             newObj = obj.Copy()
   1692             newObj.letters = obj.letters[npos:]
   1693             obj.letters = obj.letters[:npos]
   1694             stack.insert(stack.index(obj) + 1, newObj)
   1695             changeObjs.append(obj)
   1696         anchor = self.ApplyGameSelection(what, data, changeObjs)
   1697         if anchor is None:
   1698             return
   1699         if anchor == -1:
   1700             anchor = None
   1701         bBalance = 0
   1702         uBalance = 0
   1703         iBalance = 0
   1704         for obj in changeObjs:
   1705             letterAmt = len(obj.letters)
   1706             bBalance += letterAmt if obj.fontFlags & fontflags.b else -letterAmt
   1707             uBalance += letterAmt if obj.fontFlags & fontflags.u else -letterAmt
   1708             iBalance += letterAmt if obj.fontFlags & fontflags.i else -letterAmt
   1709 
   1710         for obj in changeObjs:
   1711             prevFlag = obj.fontFlags
   1712             if what == 1:
   1713                 obj.fontFlags = (fontflags.b if bBalance <= 0 else 0) | prevFlag & fontflags.i | prevFlag & fontflags.u
   1714             elif what == 2:
   1715                 obj.fontFlags = prevFlag & fontflags.b | (fontflags.i if iBalance <= 0 else 0) | prevFlag & fontflags.u
   1716             elif what == 3:
   1717                 obj.fontFlags = prevFlag & fontflags.b | prevFlag & fontflags.i | (fontflags.u if uBalance <= 0 else 0)
   1718             elif what == 4:
   1719                 obj.color = self.fontColor
   1720             elif what == 5:
   1721                 obj.fontSize = self.fontSize
   1722             elif what == 6:
   1723                 if anchor is not None:
   1724                     attr = Bunch()
   1725                     attr.href = anchor
   1726                     attr.alt = anchor
   1727                     obj.a = attr
   1728                 else:
   1729                     obj.color = (1.0, 1.0, 1.0, 0.75)
   1730                     obj.lcolor = None
   1731                     if obj.a:
   1732                         obj.fontFlags ^= fontflags.b | fontflags.u
   1733                     obj.a = None
   1734 
   1735         self.DoContentResize()
   1736         if what == 6:
   1737             self.SetSelectionRange(None, None)
   1738             self.RemoveAnchor()
   1739         uthread.new(uicore.registry.SetFocus, self)
   1740 
   1741     def ApplyGameSelection(self, *args):
   1742         pass
   1743 
   1744     def OnUp(self):
   1745         pass
   1746 
   1747     def OnDown(self):
   1748         pass
   1749 
   1750     def OnHome(self):
   1751         pass
   1752 
   1753     def OnEnd(self):
   1754         pass
   1755 
   1756     def CtrlUp(self, *args):
   1757         pass
   1758 
   1759     def CtrlDown(self, *args):
   1760         pass
   1761 
   1762     def OnClipperResize(self, clipperWidth, clipperHeight, *args, **kw):
   1763         if getattr(self, '_loading', False):
   1764             return
   1765         if getattr(self, 'noresize', 0):
   1766             return
   1767         if getattr(self, '_initingEdit', False):
   1768             return
   1769         if not self.sr.resizeTimer:
   1770             self.sr.resizeTimer = AutoTimer(100, self.DoContentResize)
   1771 
   1772     DoSizeUpdate = OnClipperResize
   1773 
   1774     def DoContentResize(self):
   1775         if getattr(self, 'resizing', 0) or self.destroyed or not hasattr(self, 'autoScrollToBottom'):
   1776             return
   1777         if self.readonly and self.sr.htmlstr:
   1778             self.resizing = 1
   1779             scrollTo = self.GetScrollProportion()
   1780             fromCharIndex, toCharIndex = self.globalSelectionRange
   1781             self.LoadHTML(None, scrollTo=scrollTo, newThread=False)
   1782             self.SetSelectionRange(fromCharIndex, toCharIndex)
   1783             self.resizing = 0
   1784             self.sr.resizeTimer = None
   1785         else:
   1786             try:
   1787                 self.resizing = 1
   1788                 self.contentHeight = 0
   1789                 self.contentWidth = 0
   1790                 if hasattr(self, 'textbuffers'):
   1791                     self.Simplify()
   1792                     for overlay, attrs, x, y in self.sr.overlays:
   1793                         overlay.state = uiconst.UI_HIDDEN
   1794 
   1795                     _lines = [ScrollEntryNode(decoClass=SE_Space, height=self.ymargin)]
   1796                     for stack, attrs in self.textbuffers:
   1797                         lines = self.BreakLines(stack)
   1798                         entries = self.BuildEntries(lines, stack, attrs)
   1799                         _lines += entries
   1800 
   1801                     if len(_lines) == 1 and not self.readonly:
   1802                         _lines.append(self.GetFirstLine())
   1803                     pos = self.GetScrollProportion()
   1804                     if self.autoScrollToBottom and pos == 0.0:
   1805                         pos = 1.0
   1806                     self.LoadContent(contentList=_lines, scrollTo=pos)
   1807                     if hasattr(self, 'CheckOverlaysAndUnderlays'):
   1808                         self.CheckOverlaysAndUnderlays()
   1809                 self.RefreshCursorAndSelection()
   1810             finally:
   1811                 self.resizing = 0
   1812 
   1813             self.sr.resizeTimer = None
   1814 
   1815     def RefreshLine(self, node, attrs, fullRefresh = False):
   1816         idx = node.idx
   1817         stack = node.stack
   1818         nodesInStack = []
   1819         for _node in self.GetNodes():
   1820             if _node.stack is stack:
   1821                 nodesInStack.append(_node)
   1822             elif nodesInStack:
   1823                 break
   1824 
   1825         if fullRefresh:
   1826             rmNodes = nodesInStack
   1827             inStackCursorPos = 0
   1828         else:
   1829             rmNodes = []
   1830             inStackCursorPos = 0
   1831             for _node in nodesInStack:
   1832                 if _node is node:
   1833                     rmNodes.append(_node)
   1834                 elif rmNodes:
   1835                     rmNodes.append(_node)
   1836                 else:
   1837                     inStackCursorPos += _node.letterCountInLine
   1838 
   1839         self.RemoveNodes(rmNodes)
   1840         self.InsertLines(stack, inStackCursorPos, idx, attrs)
   1841 
   1842     def UpdateNodes(self, oldStack, newStack):
   1843         for node in self.GetNodes():
   1844             if node.stack is oldStack:
   1845                 node.stack = newStack
   1846 
   1847     def EnlargeSize(self):
   1848         if self.sr.attribPanel:
   1849             self.sr.attribPanel.sr.comboFontSize.ShiftVal(1, 1)
   1850 
   1851     def ReduceSize(self):
   1852         if self.sr.attribPanel:
   1853             self.sr.attribPanel.sr.comboFontSize.ShiftVal(-1, 1)
   1854 
   1855     def ToggleBold(self):
   1856         self.fontFlag ^= fontflags.b
   1857         self.ApplySelection(1)
   1858         self._AttribStateChange()
   1859 
   1860     def ToggleUnderline(self):
   1861         self.fontFlag ^= fontflags.u
   1862         self.ApplySelection(3)
   1863         self._AttribStateChange()
   1864 
   1865     def ToggleItalic(self):
   1866         self.fontFlag ^= fontflags.i
   1867         self.ApplySelection(2)
   1868         self._AttribStateChange()
   1869 
   1870     def ChangeFontSize(self, newSize):
   1871         self.fontSize = newSize
   1872         self._AttribStateChange()
   1873         self.ApplySelection(5)
   1874         uicore.registry.SetFocus(self)
   1875 
   1876     def ChangeFontColor(self, newColor):
   1877         self.fontColor = newColor
   1878         self._AttribStateChange()
   1879         self.ApplySelection(4)
   1880         uicore.registry.SetFocus(self)
   1881 
   1882     def AddAnchor(self):
   1883         if self.GetSelectedText().strip() == '':
   1884             return
   1885         self.RemoveAnchor()
   1886         self.ApplySelection(6)
   1887 
   1888     def RemoveAnchor(self):
   1889         if self.href:
   1890             self.href = None
   1891             self.fontFlag &= ~(fontflags.b | fontflags.u)
   1892             self.fontColor = (1.0, 1.0, 1.0, 0.75)
   1893             self._AttribStateChange()
   1894 
   1895     def _AttribStateChange(self):
   1896         if self.sr.attribPanel is not None:
   1897             self.sr.attribPanel.AttribStateChange(bool(self.fontFlag & fontflags.b), bool(self.fontFlag & fontflags.i), bool(self.fontFlag & fontflags.u), self.fontSize, self.fontColor, self.href)
   1898 
   1899 
   1900 class EditCoreOverride(EditCore):
   1901 
   1902     @classmethod
   1903     def __reload_update__(cls, old_cls):
   1904         return old_cls