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

label.py (67062B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\carbonui\control\label.py
      2 import re
      3 import sys
      4 import types
      5 import blue
      6 import eveLocalization
      7 import localization
      8 import localization.settings
      9 import log
     10 import trinity
     11 from carbon.client.script.environment.AudioUtil import PlaySound
     12 from carbon.common.script.sys import serviceConst
     13 from carbon.common.script.util import mathUtil
     14 from carbon.common.script.util.commonutils import StripTags
     15 from carbon.common.script.util.timerstuff import AutoTimer
     16 from carbonui import fontconst, uiconst
     17 from carbonui.control.link import Link
     18 from carbonui.primitives.base import ScaleDpi, ReverseScaleDpi
     19 from carbonui.primitives.sprite import VisibleBase
     20 from carbonui.text.const import TextAlign
     21 from carbonui.text.settings import check_convert_font_size, font_shadow_enabled_setting
     22 from carbonui.uicore import uicore
     23 from carbonui.util import colorblind
     24 from carbonui.util.bunch import Bunch
     25 from carbonui.util.stringManip import GetAsUnicode
     26 from carbonui.util.various_unsorted import StringColorToHex
     27 from localization.pseudoloc import Pseudolocalize
     28 from menu import MenuLabel
     29 COLORBLIND_MAX_SATURATION = None
     30 layoutCountStat = blue.statistics.Find('CarbonUI/labelLayout')
     31 if not layoutCountStat:
     32     layoutCountStat = blue.CcpStatisticsEntry()
     33     layoutCountStat.name = 'CarbonUI/labelLayout'
     34     layoutCountStat.type = 1
     35     layoutCountStat.resetPerFrame = True
     36     layoutCountStat.description = 'The number of calls to LabelCore.Layout per frame'
     37     blue.statistics.Register(layoutCountStat)
     38 ELLIPSIS = u'\u2026'
     39 SINGLE_LINE_NEWLINE_PATTERN = re.compile('<br>|\r\n|\n')
     40 STOPWRAP = -1
     41 COLOR_WHITE_FULLALPHA = -1
     42 
     43 class LabelCore(VisibleBase):
     44     __renderObject__ = trinity.Tr2Sprite2dTextObject
     45     isDragObject = True
     46     busy = False
     47     linespace = 0
     48     default_name = 'label'
     49     default_state = uiconst.UI_DISABLED
     50     default_fontsize = None
     51     default_fontStyle = None
     52     default_fontFamily = None
     53     default_fontPath = None
     54     default_linkStyle = uiconst.LINKSTYLE_SUBTLE
     55     default_textAlign = TextAlign.LEFT
     56     default_spriteEffect = trinity.TR2_SFX_FONT
     57     default_color = None
     58     default_text = ''
     59     default_tabs = []
     60     default_tabMargin = uiconst.LABELTABMARGIN
     61     default_uppercase = False
     62     default_maxLines = None
     63     default_maxFontSize = None
     64     default_wrapMode = uiconst.WRAPMODE_FORCEWORD
     65     default_lineSpacing = fontconst.DEFAULT_LINESPACING
     66     default_letterspace = 0
     67     default_underline = 0
     68     default_bold = 0
     69     default_italic = 0
     70     default_specialIndent = 0
     71     default_autoDetectCharset = False
     72     default_showEllipsis = False
     73     default_autoUpdate = True
     74     default_shadowOffset = (0, 0)
     75     default_shadowColor = (0, 0, 0, 0.5)
     76     default_shadowSpriteEffect = None
     77     _underline = default_underline
     78     _bold = default_bold
     79     _italic = default_italic
     80     _uppercase = default_uppercase
     81     _letterspace = default_letterspace
     82     _lineSpacing = default_lineSpacing
     83     _wrapMode = default_wrapMode
     84     _maxLines = default_maxLines
     85     _tabs = default_tabs
     86     _setWidth = 0
     87     _setHeight = 0
     88     _minCursor = 0
     89     _shadowOffset = (0, 0)
     90     _alphaFadeLeft = None
     91     _alphaFadeRight = None
     92     _alphaFadeBottom = None
     93     _fontStyle = default_fontStyle
     94     _fontPath = default_fontPath
     95     _fontFamily = default_fontFamily
     96     _fontsize = default_fontsize
     97     _setText = ''
     98     _localizationText = None
     99     _parseDirty = False
    100     _layoutDirty = False
    101     _inlineObjects = None
    102     _inlineObjectsBuff = None
    103     _measurerProperties = None
    104     _lastAddTextData = None
    105     actualTextHeight = 0
    106     actualTextWidth = 0
    107     localizationWrapOn = False
    108     tooltipPosition = None
    109     auxTooltipPosition = None
    110     autoFitToText = False
    111     autoFadeSides = False
    112     _resolvingAutoSizing = False
    113     _hilite = 0
    114     _mouseOverUrl = None
    115     _mouseOverUrlID = None
    116     _mouseOverTextBuff = None
    117     _lastAbs = None
    118     _clipWidth = None
    119     _clipHeight = None
    120     autoUpdate = None
    121     _colorInt = -1
    122     _offer_auxiliary_copy_option = True
    123     _raw_fontsize = None
    124     _using_default_shadow = True
    125 
    126     def __init__(self, autoFadeSides = False, shadowOffset = None, **kwargs):
    127         self.autoFadeSides = autoFadeSides
    128         if shadowOffset is None:
    129             shadowOffset = self.get_default_shadow_offset()
    130         super(LabelCore, self).__init__(shadowOffset=shadowOffset, **kwargs)
    131 
    132     def ApplyAttributes(self, attributes):
    133         self.busy = True
    134         VisibleBase.ApplyAttributes(self, attributes)
    135         if self.default_fontsize is None:
    136             self.default_fontsize = fontconst.DEFAULT_FONTSIZE
    137         self.collectWordsInStack = attributes.get('collectWordsInStack', False)
    138         self.mouseOverWordCallback = attributes.get('mouseOverWordCallback', None)
    139         self.autoFitToText = attributes.get('autoFitToText', False)
    140         self.InitMeasurer()
    141         if self.align != uiconst.TOALL:
    142             self._setWidth = self.width
    143             self._setHeight = self.height
    144         self._textAlign = attributes.get('textAlign', self.default_textAlign)
    145         self._fontsize = None
    146         self._clipWidth = attributes.get('clipWidth', self._clipWidth)
    147         self._clipHeight = attributes.get('clipHeight', self._clipHeight)
    148         self.InitLocalizationFlag()
    149         self._tabMargin = attributes.get('tabMargin', self.default_tabMargin)
    150         self.shadowSpriteEffect = attributes.get('shadowSpriteEffect', self.default_shadowSpriteEffect)
    151         self.shadowOffset = attributes.get('shadowOffset', self.default_shadowOffset)
    152         self.shadowColor = attributes.get('shadowColor', self.default_shadowColor)
    153         self.autoUpdate = attributes.get('autoUpdate', self.default_autoUpdate)
    154         self.tabs = attributes.get('tabs', self.default_tabs)
    155         self.fontStyle = attributes.get('fontStyle', self.default_fontStyle)
    156         self.fontFamily = attributes.get('fontFamily', self.default_fontFamily)
    157         self.fontPath = attributes.get('fontPath', self.default_fontPath)
    158         if not self.fontPath and not self.fontFamily:
    159             self.fontFamily = uicore.font.GetFontFamilyBasedOnClientLanguageID()
    160         self.linkStyle = attributes.get('linkStyle', self.default_linkStyle)
    161         self._maxFontSize = attributes.get('maxFontSize', self.default_maxFontSize)
    162         self.fontsize = attributes.get('fontsize', self.default_fontsize)
    163         self.underline = attributes.get('underline', self.default_underline)
    164         self.bold = attributes.get('bold', self.default_bold)
    165         self.italic = attributes.get('italic', self.default_italic)
    166         self.uppercase = attributes.get('uppercase', self.default_uppercase)
    167         self.wrapMode = attributes.get('wrapMode', self.default_wrapMode)
    168         self.showEllipsis = attributes.get('showEllipsis', self.default_showEllipsis)
    169         self.lineSpacing = attributes.get('lineSpacing', self.default_lineSpacing)
    170         self.letterspace = attributes.get('letterspace', self.default_letterspace)
    171         self.specialIndent = attributes.get('specialIndent', self.default_specialIndent)
    172         self.autoDetectCharset = attributes.get('autoDetectCharset', self.default_autoDetectCharset)
    173         if not attributes.get('offer_auxiliary_copy_option', True):
    174             self._offer_auxiliary_copy_option = False
    175         if attributes.get('singleline', False):
    176             maxLines = 1
    177         else:
    178             maxLines = attributes.get('maxLines', self.default_maxLines)
    179         self.maxLines = maxLines
    180         self._measuringText = attributes.get('measuringText', False)
    181         self.busy = False
    182         self.text = attributes.get('text', self.default_text)
    183 
    184     @classmethod
    185     def get_default_shadow_offset(cls):
    186         if font_shadow_enabled_setting.is_enabled():
    187             return cls.default_shadowOffset
    188         else:
    189             return (0, 0)
    190 
    191     def InitMeasurer(self):
    192         self.measurer = trinity.Tr2FontMeasurer()
    193         self.renderObject.fontMeasurer = self.measurer
    194 
    195     def InitLocalizationFlag(self):
    196         self.localizationWrapOn = localization.settings.qaSettings.LocWrapSettingsActive()
    197 
    198     def Close(self):
    199         self._inlineObjects = None
    200         self._inlineObjectsBuff = None
    201         self._hiliteResetTimer = None
    202         if self.renderObject:
    203             self.renderObject.fontMeasurer = None
    204         self.measurer = None
    205         VisibleBase.Close(self)
    206 
    207     def UpdateUIScaling(self, *args):
    208         if self.destroyed or self.measurer is None:
    209             return
    210         self.measurer.fontSize = 0
    211         self.Layout('OnUIScalingChange')
    212 
    213     def ResolveAutoSizing(self):
    214         self._resolvingAutoSizing = True
    215         if self.autoFitToText:
    216             setWidth = None
    217             setHeight = None
    218         else:
    219             setWidth = self._setWidth
    220             setHeight = self._setHeight
    221         if self.clipWidth:
    222             setWidth = self.clipWidth
    223         if self.clipHeight:
    224             setHeight = self.clipHeight
    225         align = self.align
    226         width = 0
    227         height = 0
    228         if align in uiconst.ALIGNMENTS_WITH_RELEVANT_WIDTH:
    229             width += setWidth or self.textwidth
    230         if align in uiconst.ALIGNMENTS_WITH_INCLUDED_HORIZONTAL_PADDING:
    231             width += self.padLeft + self.padRight
    232         if align in uiconst.ALIGNMENTS_WITH_RELEVANT_HEIGHT:
    233             height += setHeight or self.textheight
    234         if align in uiconst.ALIGNMENTS_WITH_INCLUDED_VERTICAL_PADDING:
    235             height += self.padTop + self.padBottom
    236         self.width = width
    237         self.height = height
    238         self._resolvingAutoSizing = False
    239 
    240     def SetLeftAlphaFade(self, fadeStart = 0, maxFadeWidth = 0):
    241         if maxFadeWidth:
    242             self._alphaFadeLeft = (fadeStart, maxFadeWidth)
    243             self._UpdateAlphaFade()
    244         else:
    245             self._alphaFadeLeft = None
    246             measurer = self.measurer
    247             if measurer:
    248                 measurer.fadeLeftStart = 0
    249                 measurer.fadeLeftEnd = 0
    250 
    251     def SetRightAlphaFade(self, fadeEnd = 0, maxFadeWidth = 0):
    252         if maxFadeWidth:
    253             self._alphaFadeRight = (fadeEnd, maxFadeWidth)
    254             self._UpdateAlphaFade()
    255         else:
    256             self._alphaFadeRight = None
    257             measurer = self.measurer
    258             if measurer:
    259                 measurer.fadeRightStart = sys.maxint
    260                 measurer.fadeRightEnd = sys.maxint
    261 
    262     def SetBottomAlphaFade(self, fadeEnd = 0, maxFadeHeight = 0):
    263         if maxFadeHeight:
    264             self._alphaFadeBottom = (fadeEnd, maxFadeHeight)
    265             self._UpdateAlphaFade()
    266         else:
    267             self._alphaFadeBottom = None
    268             measurer = self.measurer
    269             if measurer:
    270                 measurer.fadeBottomStart = sys.maxint
    271                 measurer.fadeBottomEnd = sys.maxint
    272 
    273     def _UpdateAlphaFade(self):
    274         measurer = self.measurer
    275         if not measurer:
    276             return
    277         if self._alphaFadeLeft:
    278             fadeStart, length = self._alphaFadeLeft
    279             fadeStart = ScaleDpi(fadeStart - 0.5)
    280             length = ScaleDpi(length)
    281             measurer.fadeLeftStart = fadeStart
    282             measurer.fadeLeftEnd = fadeStart + length
    283         if self._alphaFadeRight:
    284             fadeEnd, length = self._alphaFadeRight
    285             if self.textwidth > fadeEnd:
    286                 diff = self.textwidth - fadeEnd
    287                 length = ScaleDpi(min(length, diff))
    288                 fadeEnd = ScaleDpi(fadeEnd - 0.5)
    289                 measurer.fadeRightStart = max(0, fadeEnd - length)
    290                 measurer.fadeRightEnd = fadeEnd
    291             else:
    292                 measurer.fadeRightStart = sys.maxint
    293                 measurer.fadeRightEnd = sys.maxint
    294         if self._alphaFadeBottom:
    295             fadeEnd, length = self._alphaFadeBottom
    296             if self.textheight > fadeEnd:
    297                 diff = self.textheight - fadeEnd
    298                 length = ScaleDpi(min(length, diff))
    299                 fadeEnd = ScaleDpi(fadeEnd - 0.5)
    300                 measurer.fadeBottomStart = max(0, fadeEnd - length)
    301                 measurer.fadeBottomEnd = fadeEnd
    302             else:
    303                 measurer.fadeBottomStart = sys.maxint
    304                 measurer.fadeBottomEnd = sys.maxint
    305 
    306     @property
    307     def fontsize(self):
    308         return self._fontsize
    309 
    310     @fontsize.setter
    311     def fontsize(self, value):
    312         converted_value = check_convert_font_size(value)
    313         if self._maxFontSize:
    314             converted_value = min(converted_value, self._maxFontSize)
    315         if converted_value != value:
    316             self._raw_fontsize = value
    317         self.SetLayoutTriggerProperty('fontsize', converted_value)
    318 
    319     @property
    320     def mincommitcursor(self):
    321         if uicore.desktop.dpiScaling != 1.0:
    322             return ReverseScaleDpi(self._minCursor + 0.5)
    323         else:
    324             return self._minCursor
    325 
    326     @property
    327     def textwidth(self):
    328         if uicore.desktop.dpiScaling != 1.0:
    329             return ReverseScaleDpi(self.actualTextWidth + 0.5)
    330         else:
    331             return self.actualTextWidth
    332 
    333     @property
    334     def textheight(self):
    335         if uicore.desktop.dpiScaling != 1.0:
    336             return ReverseScaleDpi(self.actualTextHeight + 0.5)
    337         else:
    338             return self.actualTextHeight
    339 
    340     @VisibleBase.displayRect.setter
    341     def displayRect(self, value):
    342         VisibleBase.displayRect.fset(self, value)
    343         ro = self.renderObject
    344         if ro:
    345             if self.clipWidth:
    346                 ro.textWidth = ScaleDpi(self.clipWidth)
    347             else:
    348                 ro.textWidth = ro.displayWidth
    349             if self.clipHeight:
    350                 ro.textHeight = ScaleDpi(self.clipHeight)
    351             else:
    352                 ro.textHeight = ro.displayHeight
    353 
    354     @VisibleBase.width.setter
    355     def width(self, value):
    356         VisibleBase.width.fset(self, value)
    357         if not getattr(self, '_resolvingAutoSizing', False) and self.align != uiconst.TOALL:
    358             if value != self._setWidth:
    359                 self._layoutDirty = True
    360             self._setWidth = value
    361             if getattr(self, 'autoUpdate', False) and not self.isAffectedByPushAlignment:
    362                 self.Layout('width')
    363 
    364     def clear_set_width(self):
    365         if self._setWidth is not None:
    366             self._setWidth = None
    367             self._layoutDirty = True
    368             if getattr(self, 'autoUpdate', False) and not self.isAffectedByPushAlignment:
    369                 self.Layout('clear_set_width')
    370 
    371     @VisibleBase.height.setter
    372     def height(self, value):
    373         VisibleBase.height.fset(self, value)
    374         if not getattr(self, '_resolvingAutoSizing', False) and self.align != uiconst.TOALL:
    375             self._setHeight = value
    376             if getattr(self, 'autoUpdate', False) and not self.busy and not self.isAffectedByPushAlignment:
    377                 self.Layout('height')
    378 
    379     @VisibleBase.padding.setter
    380     def padding(self, value):
    381         if isinstance(value, (tuple, list)):
    382             padLeft, padTop, padRight, padBottom = value
    383         else:
    384             padLeft = padTop = padRight = padBottom = value
    385         need_to_update_size = self._padLeft != padLeft or self._padTop != padTop or self._padRight != padRight or self._padBottom != padBottom
    386         VisibleBase.padding.fset(self, value)
    387         if need_to_update_size and not self._resolvingAutoSizing and not self.busy:
    388             self.ResolveAutoSizing()
    389 
    390     @VisibleBase.padLeft.setter
    391     def padLeft(self, value):
    392         need_to_update_size = self._padLeft != value
    393         VisibleBase.padLeft.fset(self, value)
    394         if need_to_update_size and not self._resolvingAutoSizing and not self.busy:
    395             self.ResolveAutoSizing()
    396 
    397     @VisibleBase.padTop.setter
    398     def padTop(self, value):
    399         need_to_update_size = self._padTop != value
    400         VisibleBase.padTop.fset(self, value)
    401         if need_to_update_size and not self._resolvingAutoSizing and not self.busy:
    402             self.ResolveAutoSizing()
    403 
    404     @VisibleBase.padRight.setter
    405     def padRight(self, value):
    406         need_to_update_size = self._padRight != value
    407         VisibleBase.padRight.fset(self, value)
    408         if need_to_update_size and not self._resolvingAutoSizing and not self.busy:
    409             self.ResolveAutoSizing()
    410 
    411     @VisibleBase.padBottom.setter
    412     def padBottom(self, value):
    413         need_to_update_size = self._padBottom != value
    414         VisibleBase.padBottom.fset(self, value)
    415         if need_to_update_size and not self._resolvingAutoSizing and not self.busy:
    416             self.ResolveAutoSizing()
    417 
    418     @property
    419     def clipWidth(self):
    420         return self._clipWidth
    421 
    422     @clipWidth.setter
    423     def clipWidth(self, value):
    424         self._clipWidth = value
    425         self.ResolveAutoSizing()
    426 
    427     @property
    428     def clipHeight(self):
    429         return self._clipHeight
    430 
    431     @clipHeight.setter
    432     def clipHeight(self, value):
    433         self._clipHeight = value
    434         self.ResolveAutoSizing()
    435 
    436     @property
    437     def hint(self):
    438         return getattr(self, '_hint', None)
    439 
    440     @hint.setter
    441     def hint(self, value):
    442         if not getattr(self, '_resolvingInlineHint', False):
    443             self._objectHint = value
    444         else:
    445             VisibleBase.hint.fset(self, value)
    446 
    447     @property
    448     def shadowSpriteEffect(self):
    449         return self._shadowSpriteEffect
    450 
    451     @shadowSpriteEffect.setter
    452     def shadowSpriteEffect(self, value):
    453         self._shadowSpriteEffect = value
    454         if self.renderObject:
    455             if self._shadowSpriteEffect is None:
    456                 self.renderObject.useShadowSpriteEffect = False
    457             else:
    458                 self.renderObject.useShadowSpriteEffect = True
    459                 self.renderObject.shadowSpriteEffect = self._shadowSpriteEffect
    460 
    461     @property
    462     def shadowOffset(self):
    463         return self._shadowOffset
    464 
    465     @shadowOffset.setter
    466     def shadowOffset(self, value):
    467         if self._using_default_shadow and value != self.get_default_shadow_offset():
    468             self._using_default_shadow = False
    469         self._shadowOffset = value
    470         ro = self.renderObject
    471         if ro:
    472             ro.shadowOffset = value
    473 
    474     @property
    475     def shadowColor(self):
    476         return self._shadowColor
    477 
    478     @shadowColor.setter
    479     def shadowColor(self, value):
    480         self._shadowColor = value
    481         ro = self.renderObject
    482         if ro:
    483             ro.shadowColor = value
    484 
    485     def SetAlign(self, align):
    486         if self.align != align:
    487             layout_dirty = uiconst.is_push_alignment(self.align) != uiconst.is_push_alignment(align) or uiconst.is_affected_by_push_alignment(self.align) != uiconst.is_affected_by_push_alignment(align)
    488             super(LabelCore, self).SetAlign(align)
    489             if layout_dirty:
    490                 self._layoutDirty = layout_dirty
    491                 if self.autoUpdate and not self.busy:
    492                     self.Layout('align')
    493 
    494     def SetText(self, text):
    495         if self.localizationWrapOn:
    496             if self._localizationText == text:
    497                 return
    498             self._localizationText = text
    499         if self._setText != text:
    500             self._parseDirty = True
    501             if self.localizationWrapOn and isinstance(text, basestring):
    502                 if localization.settings.qaSettings.PseudolocSettingsActive() and localization.uiutil.IsLocalizationSafeString(text):
    503                     text = Pseudolocalize(text)
    504                 try:
    505                     text = localization.uiutil.WrapStringForDisplay(text)
    506                 except Exception:
    507                     pass
    508 
    509             self._setText = text
    510             if self.autoUpdate:
    511                 self.Layout('SetText')
    512 
    513     def GetText(self):
    514         return self._setText
    515 
    516     text = property(GetText, SetText)
    517 
    518     def SetTextAlign(self, align):
    519         self._textAlign = align
    520         if self.autoUpdate:
    521             self.Layout('SetTextAlign')
    522 
    523     @property
    524     def singleline(self):
    525         return self.maxLines == 1
    526 
    527     @singleline.setter
    528     def singleline(self, value):
    529         if value:
    530             self.maxLines = 1
    531         else:
    532             self.maxLines = None
    533 
    534     def GetProperty(self, propertyName):
    535         return getattr(self, '_' + propertyName)
    536 
    537     def SetLayoutTriggerProperty(self, propertyName, value):
    538         if getattr(self, '_' + propertyName) != value:
    539             setattr(self, '_' + propertyName, value)
    540             self._layoutDirty = True
    541             if self.autoUpdate and not self.busy:
    542                 self.Layout(propertyName)
    543 
    544     fontStyle = property(lambda self: self.GetProperty('fontStyle'), lambda self, value: self.SetLayoutTriggerProperty('fontStyle', value))
    545     fontFamily = property(lambda self: self.GetProperty('fontFamily'), lambda self, value: self.SetLayoutTriggerProperty('fontFamily', value))
    546     fontPath = property(lambda self: self.GetProperty('fontPath'), lambda self, value: self.SetLayoutTriggerProperty('fontPath', value))
    547     letterspace = property(lambda self: self.GetProperty('letterspace'), lambda self, value: self.SetLayoutTriggerProperty('letterspace', value))
    548     wrapMode = property(lambda self: self.GetProperty('wrapMode'), lambda self, value: self.SetLayoutTriggerProperty('wrapMode', value))
    549     uppercase = property(lambda self: self.GetProperty('uppercase'), lambda self, value: self.SetLayoutTriggerProperty('uppercase', value))
    550     lineSpacing = property(lambda self: self.GetProperty('lineSpacing'), lambda self, value: self.SetLayoutTriggerProperty('lineSpacing', value))
    551     underline = property(lambda self: self.GetProperty('underline'), lambda self, value: self.SetLayoutTriggerProperty('underline', value))
    552     bold = property(lambda self: self.GetProperty('bold'), lambda self, value: self.SetLayoutTriggerProperty('bold', value))
    553     italic = property(lambda self: self.GetProperty('italic'), lambda self, value: self.SetLayoutTriggerProperty('italic', value))
    554     maxLines = property(lambda self: self.GetProperty('maxLines'), lambda self, value: self.SetLayoutTriggerProperty('maxLines', value))
    555     tabs = property(lambda self: self.GetProperty('tabs'), lambda self, value: self.SetLayoutTriggerProperty('tabs', value))
    556 
    557     def SetTextColor(self, color):
    558         self.SetRGBA(*color)
    559 
    560     def _ApplyColor(self):
    561         ro = self.renderObject
    562         if ro and self._color:
    563             renderObjectOpacity = max(1.0, self._color.a)
    564             ro.color = (ro.color[0],
    565              ro.color[1],
    566              ro.color[2],
    567              renderObjectOpacity)
    568             if self.color.GetBrightness() < 0.2:
    569                 self.renderObject.spriteEffect = trinity.TR2_SFX_COPY
    570             else:
    571                 self.renderObject.spriteEffect = trinity.TR2_SFX_FONT
    572         colorInt = self._GetColorAsInt()
    573         if colorInt == self._colorInt:
    574             return
    575         self._colorInt = colorInt
    576         if self.autoUpdate and not self.busy:
    577             self.Layout('SetTextColor')
    578 
    579     def _GetColorAsInt(self):
    580         color = self.GetRGBA()
    581         tricolor = trinity.TriColor()
    582         tricolor.SetRGB(*color)
    583         if len(color) != 4:
    584             tricolor.a = 1.0
    585         return tricolor.AsInt()
    586 
    587     SetDefaultColor = SetTextColor
    588 
    589     def SetTabMargin(self, margin, refresh = 1):
    590         self._tabMargin = margin
    591         if refresh:
    592             self.Layout('SetTabMargin')
    593 
    594     def GetTab(self, idx, right = None):
    595         if len(self.tabs) > idx:
    596             return self.tabs[idx]
    597         if right is not None:
    598             return right
    599 
    600     def Update(self):
    601         if self._parseDirty or self._layoutDirty:
    602             self.Layout()
    603 
    604     def Layout(self, hint = 'None', absSize = None):
    605         if getattr(self, 'busy', 0):
    606             return
    607         self.busy = True
    608         try:
    609             self._Layout(hint, absSize)
    610         finally:
    611             self.busy = False
    612 
    613     def _Layout(self, hint, absSize):
    614         layoutCountStat.Inc()
    615         self._layoutDirty = False
    616         if self.measurer:
    617             self.measurer.Reset()
    618         self.actualTextWidth = 0
    619         self.actualTextHeight = 0
    620         text = self.text
    621         if text is None or isinstance(text, basestring) and not text:
    622             return
    623         self._urlIDCounter = 0
    624         if self._parseDirty:
    625             textToParse = GetAsUnicode(text)
    626             if self.maxLines == 1:
    627                 textToParse = SINGLE_LINE_NEWLINE_PATTERN.sub(' ', textToParse)
    628             parsePrepared = trinity.ParseLabelText(textToParse)
    629             self._parsePrepared = parsePrepared
    630             self._parseDirty = False
    631         else:
    632             parsePrepared = self._parsePrepared
    633         availableWidth = self._GetAvailableWidth(absSize)
    634         self._minCursor = None
    635         self.ResetTagStack()
    636         self._lastAddTextData = []
    637         self._inlineObjects = None
    638         self._inlineObjectsBuff = None
    639         vScrollshiftX = getattr(self, 'xShift', 0)
    640         margin = self._tabMargin
    641         self._numLines = 0
    642         self._commitCursorYScaled = 0
    643         self._currTextAlign = self._textAlign
    644         self._canPushText = True
    645         maxLines = self.maxLines
    646         for lineData in parsePrepared:
    647             left = 0
    648             isTabbed = len(lineData) > 1
    649             lineStartCursorYScaled = self._commitCursorYScaled
    650             lineMaxCommitCursorYScaled = lineStartCursorYScaled
    651             for tabIndex, tabData in enumerate(lineData):
    652                 self._commitCursorYScaled = lineStartCursorYScaled
    653                 if self.measurer:
    654                     self.measurer.font = ''
    655                 else:
    656                     return
    657                 if isTabbed:
    658                     self._currTextAlign = TextAlign.LEFT
    659                     self._canPushText = True
    660                     if availableWidth is None:
    661                         availableWidth = uicore.desktop.width
    662                     right = self.GetTab(tabIndex, availableWidth) - margin
    663                     wrapWidth = right + vScrollshiftX - left
    664                 else:
    665                     wrapWidth = availableWidth
    666                 self.ProcessLineData(tabData, left, wrapWidth)
    667                 if self._lastAddTextData:
    668                     self.CommitBuffer(doLineBreak=True)
    669                     if maxLines and self._numLines >= maxLines:
    670                         self._canPushText = False
    671                 if isTabbed:
    672                     left = right + margin * 2 + vScrollshiftX
    673                 lineMaxCommitCursorYScaled = max(lineMaxCommitCursorYScaled, self._commitCursorYScaled)
    674 
    675             self._commitCursorYScaled = lineStartCursorYScaled + (lineMaxCommitCursorYScaled - lineStartCursorYScaled)
    676 
    677         if not self._measuringText:
    678             self.ResolveAutoSizing()
    679             self._UpdateAlphaFade()
    680 
    681     def _GetAvailableWidth(self, absSize):
    682         if not self.isAffectedByPushAlignment:
    683             if self._setWidth:
    684                 availableWidth = self._setWidth
    685             else:
    686                 availableWidth = self.maxWidth
    687         elif absSize:
    688             availableWidth, _ = absSize
    689         elif self.align not in uiconst.ALIGNMENTS_WITH_RELEVANT_WIDTH:
    690             availableWidth, _ = self.GetAbsoluteSize()
    691         else:
    692             availableWidth = None
    693         return availableWidth
    694 
    695     def ResetTagStack(self):
    696         self._tagStack = {'font': [],
    697          'file': [],
    698          'fontsize': [self.fontsize],
    699          'color': [self._colorInt],
    700          'letterspace': [self.letterspace],
    701          'hint': [],
    702          'link': [],
    703          'localized': [],
    704          'localizedQA': [],
    705          'u': self.underline,
    706          'b': self.bold,
    707          'i': self.italic,
    708          'uppercase': self.uppercase}
    709 
    710     @property
    711     def maxWidth(self):
    712         if self._maxWidth is not None:
    713             return self._maxWidth
    714         try:
    715             return trinity.adapters.GetMaxTextureSize(trinity.device.adapter)
    716         except:
    717             return 0
    718 
    719     @maxWidth.setter
    720     def maxWidth(self, width):
    721         self._maxWidth = width
    722         self.Layout('maxWidth')
    723 
    724     def PushText(self, text, measurer, oneLiner, wrapModeForceAll, maxLines):
    725         if not measurer:
    726             return STOPWRAP
    727         textAdded = measurer.AddText(text)
    728         if textAdded >= len(text):
    729             self._lastAddTextData.append((text, self._measurerProperties))
    730             return
    731         if oneLiner:
    732             if self.showEllipsis:
    733                 sliceBack = 0
    734                 while textAdded > sliceBack:
    735                     tryFit = text[:textAdded - sliceBack] + ELLIPSIS
    736                     measurer.CancelLastText()
    737                     ellipsisFit = measurer.AddText(tryFit)
    738                     if ellipsisFit == len(tryFit):
    739                         break
    740                     sliceBack += 1
    741 
    742             self.CommitBuffer()
    743             return STOPWRAP
    744         measurer.CancelLastText()
    745         hasData = bool(self._lastAddTextData)
    746         if not hasData:
    747             if textAdded == 0:
    748                 return STOPWRAP
    749         if not wrapModeForceAll:
    750             wrapPointInText = self.FindWrapPointInText(text, textAdded)
    751             if wrapPointInText is not None:
    752                 textAdded = wrapPointInText
    753             elif hasData:
    754                 lastText, lastMeasurerProperties = self._lastAddTextData[-1]
    755                 wpl = eveLocalization.WrapPointList(lastText + text, session.languageID)
    756                 combinedResult = wpl.GetLinebreakPoints()
    757                 if len(lastText) in combinedResult:
    758                     self.CommitBuffer(doLineBreak=True)
    759                     if maxLines and self._numLines >= maxLines:
    760                         return STOPWRAP
    761                     return self.PushText(text, measurer, oneLiner, wrapModeForceAll, maxLines)
    762                 lineText = u''.join([ addedData[0] for addedData in self._lastAddTextData ])
    763                 wpl = eveLocalization.WrapPointList(lineText, session.languageID)
    764                 lineWrapPoints = wpl.GetLinebreakPoints()
    765                 if lineWrapPoints:
    766                     linePos = len(lineText)
    767                     moveToNextLine = []
    768                     breakAt = lineWrapPoints[-1]
    769                     while self._lastAddTextData:
    770                         addedData = self._lastAddTextData.pop()
    771                         addedText, addedMeasurerProperties = addedData
    772                         addedTextLength = len(addedText)
    773                         addedTextPos = linePos - addedTextLength
    774                         measurer.CancelLastText()
    775                         if addedTextPos <= breakAt <= linePos:
    776                             self.SetMeasurerProperties(*addedMeasurerProperties)
    777                             measurer.AddText(addedText[:breakAt - addedTextPos])
    778                             self._lastAddTextData.append((addedText[:breakAt - addedTextPos], addedMeasurerProperties))
    779                             rest = addedText[breakAt - addedTextPos:]
    780                             if rest:
    781                                 moveToNextLine.insert(0, (rest, addedMeasurerProperties))
    782                             break
    783                         else:
    784                             moveToNextLine.insert(0, addedData)
    785                         linePos -= addedTextLength
    786 
    787                     self.CommitBuffer(doLineBreak=True)
    788                     if maxLines and self._numLines >= maxLines:
    789                         return STOPWRAP
    790                     for nextLineData in moveToNextLine:
    791                         addedText, addedMeasurerProperties = nextLineData
    792                         self.SetMeasurerProperties(*addedMeasurerProperties)
    793                         measurer.AddText(addedText)
    794                         self._lastAddTextData.append(nextLineData)
    795 
    796                     self.SetMeasurerProperties(*self._measurerProperties)
    797                     return self.PushText(text, measurer, oneLiner, wrapModeForceAll, maxLines)
    798         textSlice = text[:textAdded]
    799         measurer.AddText(textSlice)
    800         self._lastAddTextData.append((textSlice, self._measurerProperties))
    801         self.CommitBuffer(doLineBreak=True)
    802         if maxLines and self._numLines >= maxLines:
    803             return STOPWRAP
    804         moveToNext = text[textAdded:]
    805         return self.PushText(moveToNext, measurer, oneLiner, wrapModeForceAll, maxLines)
    806 
    807     def SetMeasurerProperties(self, fontsize, color, letterspace, underline, fontPath, register = False):
    808         measurer = self.measurer
    809         if not measurer:
    810             return
    811         measurer.fontSize = fontsize
    812         try:
    813             measurer.color = color
    814         except TypeError as err:
    815             log.LogError('Invalid color passed to text renderer, error: %s, color: %s' % (err, color))
    816             measurer.color = COLOR_WHITE_FULLALPHA
    817 
    818         measurer.letterSpace = letterspace
    819         measurer.underline = underline
    820         if fontPath is not None:
    821             measurer.font = str(fontPath)
    822         else:
    823             measurer.font = ''
    824         if register:
    825             self._measurerProperties = (fontsize,
    826              color,
    827              letterspace,
    828              underline,
    829              fontPath)
    830 
    831     def ProcessLineData(self, lineData, left, wrapWidth):
    832         if wrapWidth is not None:
    833             self._wrapWidthScaled = ScaleDpi(wrapWidth)
    834         else:
    835             self._wrapWidthScaled = None
    836         self._commitCursorXScaled = ScaleDpi(left)
    837         measurer = self.measurer
    838         if not measurer:
    839             return False
    840         measurer.limit = self._wrapWidthScaled or 0
    841         measurer.cursorX = 0
    842         setHeight = self._setHeight
    843         maxLines = self.maxLines
    844         oneLiner = maxLines == 1
    845         wrapMode = self.wrapMode
    846         wrapModeForceAll = wrapMode == uiconst.WRAPMODE_FORCEALL
    847         tagStackDirty = True
    848         tagStack = self._tagStack
    849         thereWasText = False
    850         for element in lineData:
    851             type = element[0]
    852             if type == 0:
    853                 text = element[1]
    854                 if text:
    855                     thereWasText = True
    856                     if getattr(self, 'collectWordsInStack', False):
    857                         textList = re.split('([\\W]+)', text)
    858                         for eachWord in textList:
    859                             if eachWord != ' ':
    860                                 tagStackDirty = self.ParseWordTag(eachWord) or tagStackDirty
    861                             tagStackDirty = self.DoTextPushing(tagStackDirty, measurer, tagStack, eachWord, oneLiner, wrapModeForceAll, maxLines)
    862                             if eachWord != ' ':
    863                                 tagStackDirty = self.ParseWordClose() or tagStackDirty
    864 
    865                     else:
    866                         tagStackDirty = self.DoTextPushing(tagStackDirty, measurer, tagStack, text, oneLiner, wrapModeForceAll, maxLines)
    867                     linkStack = tagStack.get('link', None)
    868                     if linkStack:
    869                         linkStack[-1].textBuff.append(text)
    870             elif type == 1:
    871                 tagStackDirty = self.tagIDToFunctionMapping[element[1]][0](self, element[2]) or tagStackDirty
    872             elif type == 2:
    873                 tagStackDirty = self.tagIDToFunctionMapping[element[1]][1](self) or tagStackDirty
    874             elif type == 3:
    875                 if element[1] != 'loc' and session.role & serviceConst.ROLE_PROGRAMMER:
    876                     log.LogWarn("Unknown tag '%s' in %s" % (element[1], lineData))
    877             else:
    878                 log.LogError('Unknown element type ID in ProcessLineData', type)
    879 
    880         if not thereWasText:
    881             if tagStackDirty:
    882                 self.UpdateMeasurerProperties(measurer, tagStack, u' ')
    883                 tagStackDirty = False
    884             ret = self.PushText(u' ', measurer, oneLiner, wrapModeForceAll, maxLines)
    885         return tagStackDirty
    886 
    887     def DoTextPushing(self, tagStackDirty, measurer, tagStack, myText, oneLiner, wrapModeForceAll, maxLines):
    888         if tagStackDirty:
    889             self.UpdateMeasurerProperties(measurer, tagStack, myText)
    890             tagStackDirty = False
    891         if self._canPushText:
    892             if tagStack['uppercase']:
    893                 myText = localization.util.UpperCase(myText)
    894             ret = self.PushText(myText, measurer, oneLiner, wrapModeForceAll, maxLines)
    895             if ret == STOPWRAP:
    896                 self._canPushText = False
    897         return tagStackDirty
    898 
    899     def FindWrapPointInText(self, text, fromIndex):
    900         space = u' '
    901         if text[fromIndex] != space and text[fromIndex - 1] == space:
    902             return fromIndex
    903         totalLength = len(text)
    904         if text[fromIndex] == space:
    905             spaceIndex = fromIndex
    906             while spaceIndex < totalLength:
    907                 if text[spaceIndex] != space:
    908                     break
    909                 spaceIndex += 1
    910 
    911             return spaceIndex
    912         wpl = eveLocalization.WrapPointList(unicode(text), session.languageID)
    913         result = wpl.GetLinebreakPoints()
    914         if result:
    915             retIndex = [ each for each in result if each <= fromIndex ]
    916             if retIndex:
    917                 return retIndex[-1]
    918 
    919     def UpdateMeasurerProperties(self, measurer, tagStack, words):
    920         fontsize = ScaleDpi(tagStack['fontsize'][-1])
    921         color = tagStack['color'][-1]
    922         letterspace = tagStack['letterspace'][-1]
    923         underline = bool(tagStack['u'])
    924         italic = bool(tagStack['i'])
    925         bold = bool(tagStack['b'])
    926         try:
    927             fontPath = 'res:/UI/Fonts/%s' % tagStack['file'][-1]
    928             if not blue.paths.exists(fontPath):
    929                 log.LogWarn('Font file not found:', fontPath)
    930                 fontPath = self.fontPath
    931         except IndexError:
    932             fontPath = self.fontPath
    933 
    934         fontFamily = self.fontFamily
    935         if fontPath is None:
    936             if self.autoDetectCharset:
    937                 windowsLanguageID = uicore.font.GetWindowsLanguageIDForText(words)
    938                 if windowsLanguageID:
    939                     fontFamily = uicore.font.GetFontFamilyBasedOnWindowsLanguageID(windowsLanguageID)
    940             fontPath = uicore.font.GetFontPathFromFontFamily(fontFamily, self.fontStyle, bold, italic)
    941         if self.localizationWrapOn and localization.settings.qaSettings.GetValue('showHardcodedStrings'):
    942             if not tagStack['localizedQA']:
    943                 color = localization.uiutil.COLOR_HARDCODED
    944         self.SetMeasurerProperties(fontsize, color, letterspace, underline, fontPath, register=True)
    945 
    946     def GetIndexUnderPos(self, layoutPosition):
    947         index = self.measurer.GetIndexAtPos(ScaleDpi(layoutPosition))
    948         width = ReverseScaleDpi(self.measurer.GetWidthAtIndex(index))
    949         return (index, width)
    950 
    951     def GetWidthToIndex(self, index):
    952         if self.destroyed:
    953             return
    954         if index == -1:
    955             maxLength = len(StripTags(self.text))
    956             index = maxLength
    957         width = ReverseScaleDpi(self.measurer.GetWidthAtIndex(index))
    958         return (index, width)
    959 
    960     def CommitBuffer(self, doLineBreak = False):
    961         measurer = self.measurer
    962         if not measurer:
    963             return
    964         buffWidth = measurer.cursorX
    965         cursorX = self._commitCursorXScaled
    966         textAlign = self._currTextAlign
    967         if textAlign != TextAlign.LEFT:
    968             lastAddTextData = self._lastAddTextData
    969             while lastAddTextData:
    970                 lastAddData = lastAddTextData.pop()
    971                 lastText, lastMeasurerProperties = lastAddData
    972                 if not lastText:
    973                     measurer.CancelLastText()
    974                     continue
    975                 lastTextStriped = lastText.rstrip()
    976                 if lastText != lastTextStriped:
    977                     measurer.CancelLastText()
    978                     self.SetMeasurerProperties(*lastMeasurerProperties)
    979                     measurer.AddText(lastTextStriped)
    980                     buffWidth = measurer.cursorX
    981                 break
    982 
    983             if textAlign == TextAlign.RIGHT:
    984                 if self._wrapWidthScaled is None:
    985                     absWidth, _ = self.GetAbsoluteSize()
    986                     self._wrapWidthScaled = ScaleDpi(absWidth)
    987                 cursorX += self._wrapWidthScaled - buffWidth
    988             elif textAlign == TextAlign.CENTER:
    989                 if self._wrapWidthScaled is None:
    990                     absWidth, _ = self.GetAbsoluteSize()
    991                     self._wrapWidthScaled = ScaleDpi(absWidth)
    992                 cursorX += int((self._wrapWidthScaled - buffWidth) / 2)
    993         lineHeight = measurer.ascender - measurer.descender
    994         lineSpacing = int(self.lineSpacing * lineHeight)
    995         moveToNextLine = []
    996         if self._inlineObjectsBuff:
    997             for object in self._inlineObjectsBuff:
    998                 registerObject = object.Copy()
    999                 if object.inlineXEnd is None:
   1000                     object.inlineX = 0
   1001                     moveToNextLine.append(object)
   1002                     registerObject.inlineXEnd = ReverseScaleDpi(measurer.cursorX)
   1003                 registerObject.inlineX += ReverseScaleDpi(cursorX)
   1004                 registerObject.inlineXEnd += ReverseScaleDpi(cursorX)
   1005                 registerObject.inlineY = ReverseScaleDpi(self._commitCursorYScaled)
   1006                 registerObject.inlineHeight = ReverseScaleDpi(lineHeight + lineSpacing)
   1007                 if self._inlineObjects is None:
   1008                     self._inlineObjects = []
   1009                 self._inlineObjects.append(registerObject)
   1010 
   1011         measurer.CommitText(cursorX, self._commitCursorYScaled + measurer.ascender)
   1012         if self._minCursor is None:
   1013             self._minCursor = cursorX
   1014         else:
   1015             self._minCursor = min(self._minCursor, cursorX)
   1016         self.actualTextWidth = max(self.actualTextWidth, cursorX + buffWidth)
   1017         self.actualTextHeight = max(self.actualTextHeight, self._commitCursorYScaled + lineHeight)
   1018         self._inlineObjectsBuff = moveToNextLine
   1019         self._lastAddTextData = []
   1020         if doLineBreak:
   1021             self._commitCursorYScaled += lineSpacing + lineHeight
   1022             measurer.cursorX = ScaleDpi(self.specialIndent)
   1023             self._numLines += 1
   1024 
   1025     def ParseFontOpen(self, attribs):
   1026         try:
   1027             self.ParseColorTag(attribs[u'color'])
   1028         except KeyError:
   1029             pass
   1030 
   1031         try:
   1032             self.ParseFontsizeTag(attribs[u'size'])
   1033         except KeyError:
   1034             pass
   1035 
   1036         try:
   1037             self.ParseFontfileTag(attribs[u'file'])
   1038         except KeyError:
   1039             pass
   1040 
   1041         self._tagStack['font'].append(attribs)
   1042         return True
   1043 
   1044     def ParseFontClose(self):
   1045         tagStack = self._tagStack['font']
   1046         if tagStack:
   1047             attribs = tagStack.pop()
   1048             if u'color' in attribs:
   1049                 self.ParseColorClose()
   1050             if u'size' in attribs:
   1051                 self.ParseFontsizeClose()
   1052             if u'file' in attribs:
   1053                 self.ParseFontfileClose()
   1054         return True
   1055 
   1056     def ParseUOpen(self, attribs):
   1057         self._tagStack['u'] += 1
   1058         return self._tagStack['u'] == 1
   1059 
   1060     def ParseIOpen(self, attribs):
   1061         self._tagStack['i'] += 1
   1062         return self._tagStack['i'] == 1
   1063 
   1064     def ParseBOpen(self, attribs):
   1065         self._tagStack['b'] += 1
   1066         return self._tagStack['b'] == 1
   1067 
   1068     def ParseUppercaseOpen(self, attribs):
   1069         self._tagStack['uppercase'] += 1
   1070         return self._tagStack['uppercase'] == 1
   1071 
   1072     def ParseUClose(self):
   1073         retval = self._tagStack['u'] == 1
   1074         self._tagStack['u'] = max(self._tagStack['u'] - 1, 0)
   1075         return retval
   1076 
   1077     def ParseIClose(self):
   1078         retval = self._tagStack['i'] == 1
   1079         self._tagStack['i'] = max(self._tagStack['i'] - 1, 0)
   1080         return retval
   1081 
   1082     def ParseBClose(self):
   1083         retval = self._tagStack['b'] == 1
   1084         self._tagStack['b'] = max(self._tagStack['b'] - 1, 0)
   1085         return retval
   1086 
   1087     def ParseUppercaseClose(self):
   1088         retval = self._tagStack['uppercase'] == 1
   1089         self._tagStack['uppercase'] = max(self._tagStack['uppercase'] - 1, 0)
   1090         return retval
   1091 
   1092     def ParseAOpen(self, attribs):
   1093         try:
   1094             url = attribs[u'href'].replace('&amp;', '&')
   1095         except (KeyError, TypeError):
   1096             log.LogError('Anchor tag missing href attribute, I have no idea what to do with this.  Attribs:', attribs)
   1097             return False
   1098 
   1099         alt = attribs.get('alt', None)
   1100         linkText = 'a href=' + attribs[u'href'] + (" alt='" + alt + "'" if alt is not None else '')
   1101         linkStyle = attribs.get('linkStyle', self.linkStyle)
   1102         try:
   1103             linkStyle = int(linkStyle)
   1104         except ValueError:
   1105             linkStyle = self.linkStyle
   1106 
   1107         currentTagStackSyntax = self.GetCurrentTagStackFormatSyntax()
   1108         inlineObject = self.StartInline('link', linkText)
   1109         inlineObject.url = url
   1110         inlineObject.urlID = self._urlIDCounter
   1111         inlineObject.alt = alt
   1112         inlineObject.tagStackState = self._tagStack.copy()
   1113         inlineObject.textBuff = [currentTagStackSyntax + '<' + linkText + '>']
   1114         self._tagStack['link'].append(inlineObject)
   1115         linkState = uiconst.LINK_IDLE
   1116         if self._mouseOverUrl and url == self._mouseOverUrl and inlineObject.urlID == self._mouseOverUrlID:
   1117             if uicore.uilib.leftbtn:
   1118                 linkState = uiconst.LINK_ACTIVE
   1119             else:
   1120                 linkState = uiconst.LINK_HOVER
   1121         linkFmt = self.GetLinkHandler().GetLinkFormat(url, linkState, linkStyle)
   1122         linkColor = None
   1123         if linkState == uiconst.LINK_IDLE:
   1124             try:
   1125                 colorSyntax = attribs[u'color'].replace('#', '0x')
   1126                 hexColor = StringColorToHex(colorSyntax) or colorSyntax
   1127                 if hexColor:
   1128                     linkColor = mathUtil.LtoI(long(hexColor, 0))
   1129             except KeyError:
   1130                 pass
   1131 
   1132         inlineObject.bold = linkFmt.bold
   1133         inlineObject.italic = linkFmt.italic
   1134         inlineObject.underline = linkFmt.underline
   1135         if linkFmt.bold:
   1136             self.ParseBOpen({})
   1137         if linkFmt.italic:
   1138             self.ParseIOpen({})
   1139         if linkFmt.underline:
   1140             self.ParseUOpen({})
   1141         self._tagStack['color'].append(linkColor or linkFmt.color or self._colorInt)
   1142         self._urlIDCounter += 1
   1143         return True
   1144 
   1145     def ParseLinkClose(self):
   1146         self.EndInline('link')
   1147         if self._tagStack['link']:
   1148             closingLink = self._tagStack['link'].pop()
   1149             if closingLink.bold:
   1150                 self.ParseBClose()
   1151             if closingLink.italic:
   1152                 self.ParseIClose()
   1153             if closingLink.underline:
   1154                 self.ParseUClose()
   1155             self.ParseColorClose()
   1156         return True
   1157 
   1158     def ParseLocalizedOpen(self, attribs):
   1159         if self.localizationWrapOn:
   1160             self._tagStack['localizedQA'].append(True)
   1161             return True
   1162         try:
   1163             newText = attribs['hint']
   1164             if len(self._tagStack.get('link', [])) < 1:
   1165                 self.renderObject.hasAuxiliaryTooltip = True
   1166                 inlineObject = self.StartInline('localized', newText)
   1167                 self._tagStack['localized'].append(inlineObject)
   1168             else:
   1169                 for eachLink in self._tagStack.get('link', []):
   1170                     eachLink['extraAlt'] = newText
   1171 
   1172         except KeyError:
   1173             log.LogError('Localization tag without tooltip!')
   1174 
   1175         return True
   1176 
   1177     def ParseLocalizedClose(self):
   1178         try:
   1179             if self.localizationWrapOn:
   1180                 self._tagStack['localizedQA'].pop()
   1181                 return True
   1182             self.EndInline('localized')
   1183             if len(self._tagStack['localized']) > 0:
   1184                 self._tagStack['localized'].pop()
   1185             return True
   1186         except (KeyError, IndexError):
   1187             pass
   1188 
   1189         return True
   1190 
   1191     def ParseLeftOpen(self, attribs):
   1192         self._currTextAlign = TextAlign.LEFT
   1193         return True
   1194 
   1195     def ParseRightOpen(self, attribs):
   1196         self._currTextAlign = TextAlign.RIGHT
   1197         return True
   1198 
   1199     def ParseCenterOpen(self, attribs):
   1200         self._currTextAlign = TextAlign.CENTER
   1201         return True
   1202 
   1203     def ParseColorTag(self, value):
   1204         color = StringColorToHex(value)
   1205         if color is None:
   1206             color = value.replace('#', '0x')
   1207         try:
   1208             color = colorblind.CheckReplaceColorHex(color, maintainBrightness=True, maxSaturation=COLORBLIND_MAX_SATURATION)
   1209             col = mathUtil.LtoI(long(color, 0))
   1210             self._tagStack['color'].append(col)
   1211         except ValueError:
   1212             log.LogWarn('Label got color value it cannot handle', value, self.text)
   1213             return False
   1214 
   1215         return True
   1216 
   1217     def ParseColorClose(self):
   1218         try:
   1219             if len(self._tagStack['color']) > 1:
   1220                 self._tagStack['color'].pop()
   1221             else:
   1222                 log.LogWarn('Label got ParseColorClose but doesnt have color to close', self.text)
   1223                 return False
   1224             return True
   1225         except (KeyError, IndexError):
   1226             pass
   1227 
   1228         return False
   1229 
   1230     def ParseFontsizeTag(self, value):
   1231         fs = check_convert_font_size(int(value))
   1232         if 'fontsize' not in self._tagStack:
   1233             self._tagStack['fontsize'] = []
   1234         self._tagStack['fontsize'].append(fs)
   1235         return True
   1236 
   1237     def ParseFontsizeClose(self):
   1238         try:
   1239             if len(self._tagStack['fontsize']) > 1:
   1240                 self._tagStack['fontsize'].pop()
   1241             else:
   1242                 log.LogWarn('Label got FontsizeClose but doesnt have fontsize to close', self.text)
   1243                 return False
   1244             return True
   1245         except (KeyError, IndexError):
   1246             pass
   1247 
   1248         return False
   1249 
   1250     def ParseFontfileTag(self, value):
   1251         if 'file' not in self._tagStack:
   1252             self._tagStack['file'] = []
   1253         self._tagStack['file'].append(value)
   1254         return True
   1255 
   1256     def ParseFontfileClose(self):
   1257         try:
   1258             if len(self._tagStack['file']) > 0:
   1259                 x = self._tagStack['file'].pop()
   1260             else:
   1261                 log.LogWarn('Label got FontpathClose but doesnt have file to close', self.text)
   1262                 return False
   1263             return True
   1264         except (KeyError, IndexError):
   1265             pass
   1266 
   1267         return False
   1268 
   1269     def ParseLetterspaceTag(self, value):
   1270         ls = int(value)
   1271         if 'letterspace' not in self._tagStack:
   1272             self._tagStack['letterspace'] = []
   1273         self._tagStack['letterspace'].append(ls)
   1274         return True
   1275 
   1276     def ParseLetterspaceClose(self):
   1277         try:
   1278             if len(self._tagStack['letterspace']) > 1:
   1279                 self._tagStack['letterspace'].pop()
   1280             else:
   1281                 log.LogWarn('Label got ParseLetterspaceClose but doesnt have letterspace to close', self.text)
   1282                 return False
   1283             return True
   1284         except (KeyError, IndexError):
   1285             pass
   1286 
   1287         return False
   1288 
   1289     def ParseHintTag(self, value):
   1290         inlineObject = self.StartInline('hint', value)
   1291         self._tagStack['hint'].append(inlineObject)
   1292         return True
   1293 
   1294     def ParseHintClose(self):
   1295         self.EndInline('hint')
   1296         try:
   1297             self._tagStack['hint'].pop()
   1298             return True
   1299         except (KeyError, IndexError):
   1300             pass
   1301 
   1302         return False
   1303 
   1304     def ParseWordTag(self, value):
   1305         inlineObject = self.StartInline('words', value)
   1306         if 'words' not in self._tagStack:
   1307             self._tagStack['words'] = []
   1308         self._tagStack['words'].append(inlineObject)
   1309         return True
   1310 
   1311     def ParseWordClose(self):
   1312         self.EndInline('words')
   1313         try:
   1314             self._tagStack['words'].pop()
   1315             return True
   1316         except (KeyError, IndexError):
   1317             pass
   1318 
   1319         return False
   1320 
   1321     def ParseEmptyOpen(self, attribs):
   1322         if attribs is None:
   1323             log.LogWarn('Got None attribs into ParseEmptyOpen', self.text)
   1324             return False
   1325         if u'url' in attribs:
   1326             attribs[u'href'] = attribs[u'url']
   1327             del attribs[u'url']
   1328         if u'href' in attribs:
   1329             return self.ParseAOpen(attribs)
   1330         stackDirty = False
   1331         for attrib, value in attribs.iteritems():
   1332             try:
   1333                 stackDirty = self.emptyTagHandlers[attrib](self, value) or stackDirty
   1334             except KeyError:
   1335                 log.LogWarn('Empty tag attribute', attrib, 'not recognized')
   1336 
   1337         return stackDirty
   1338 
   1339     def ParseUnusedClose(self, tag):
   1340         log.LogWarn('Unused close tag:', tag)
   1341 
   1342     def ParseUnusedOpen(self, tag):
   1343         log.LogWarn('Unused open tag:', tag)
   1344 
   1345     tagIDToFunctionMapping = {1: (ParseFontOpen, ParseFontClose),
   1346      2: (ParseUOpen, ParseUClose),
   1347      3: (ParseUppercaseOpen, ParseUppercaseClose),
   1348      4: (ParseIOpen, ParseIClose),
   1349      5: (ParseBOpen, ParseBClose),
   1350      6: (ParseAOpen, ParseLinkClose),
   1351      7: (ParseLocalizedOpen, ParseLocalizedClose),
   1352      100: (ParseLeftOpen, lambda self: self.ParseUnusedClose('left')),
   1353      101: (ParseRightOpen, lambda self: self.ParseUnusedClose('right')),
   1354      102: (ParseCenterOpen, lambda self: self.ParseUnusedClose('center')),
   1355      200: (lambda self, attribs: self.ParseUnusedOpen('color'), ParseColorClose),
   1356      201: (lambda self, attribs: self.ParseUnusedOpen('fontsize'), ParseFontsizeClose),
   1357      202: (lambda self, attribs: self.ParseUnusedOpen('letterspace'), ParseLetterspaceClose),
   1358      203: (lambda self, attribs: self.ParseUnusedOpen('hint'), ParseHintClose),
   1359      204: (lambda self, attribs: self.ParseUnusedOpen('url'), ParseLinkClose),
   1360      -48879: (ParseEmptyOpen, lambda self: self.ParseUnusedClose('empty?!?'))}
   1361     emptyTagHandlers = {u'color': ParseColorTag,
   1362      u'fontsize': ParseFontsizeTag,
   1363      u'letterspace': ParseLetterspaceTag,
   1364      u'hint': ParseHintTag,
   1365      u'file': ParseFontfileTag}
   1366 
   1367     @classmethod
   1368     def ExtractLocalizedTags(cls, text):
   1369         ret = []
   1370         tagID = 7
   1371         parsePrepared = trinity.ParseLabelText(text)
   1372         for lineData in parsePrepared:
   1373             for tabData in lineData:
   1374                 for element in tabData:
   1375                     type = element[0]
   1376                     if type == 1 and element[1] == tagID:
   1377                         ret.append(element[2])
   1378 
   1379         return ret
   1380 
   1381     def GetCurrentTagStackFormatSyntax(self, ignoreTags = ('link', 'localized', 'letterspace')):
   1382         formatSyntax = ''
   1383         for tag, stack in self._tagStack.iteritems():
   1384             if tag in ignoreTags:
   1385                 continue
   1386             if type(stack) == types.ListType:
   1387                 if stack:
   1388                     value = stack[-1]
   1389                     if tag == 'color':
   1390                         value = self.IntColorToSyntax(value)
   1391                     formatSyntax += '<%s=%s>' % (tag, value)
   1392             elif stack:
   1393                 formatSyntax += '<%s>' % tag
   1394 
   1395         return formatSyntax
   1396 
   1397     def IntColorToSyntax(self, intColor):
   1398         c = trinity.TriColor()
   1399         c.FromInt(intColor)
   1400         color = (c.r,
   1401          c.g,
   1402          c.b,
   1403          c.a)
   1404         return '#%02x%02x%02x%02x' % (color[3] * 255,
   1405          color[0] * 255,
   1406          color[1] * 255,
   1407          color[2] * 255)
   1408 
   1409     def GetTooltipPosition(self):
   1410         if self.tooltipPosition:
   1411             return self.tooltipPosition
   1412         l, t = self.GetAbsolutePosition()
   1413         w, h = self.textwidth, self.textheight
   1414         if self._alphaFadeLeft:
   1415             fadeStart, length = self._alphaFadeLeft
   1416             l += fadeStart
   1417             w -= fadeStart
   1418         if self._alphaFadeRight:
   1419             fadeEnd, length = self._alphaFadeRight
   1420             w = min(w, fadeEnd)
   1421         return (l,
   1422          t,
   1423          w,
   1424          h)
   1425 
   1426     def GetMouseOverUrl(self):
   1427         return self._mouseOverUrl
   1428 
   1429     def StartInline(self, inlineType, data):
   1430         inlineObject = Bunch()
   1431         inlineObject.inlineType = inlineType
   1432         inlineObject.data = data
   1433         measurer = self.measurer
   1434         inlineObject.inlineX = ReverseScaleDpi(measurer.cursorX)
   1435         inlineObject.inlineXEnd = None
   1436         if self._inlineObjectsBuff is None:
   1437             self._inlineObjectsBuff = []
   1438         self._inlineObjectsBuff.append(inlineObject)
   1439         return inlineObject
   1440 
   1441     def EndInline(self, inlineType):
   1442         if inlineType in self._tagStack and self._tagStack[inlineType]:
   1443             inlineObject = self._tagStack[inlineType][-1]
   1444             if inlineObject:
   1445                 measurer = self.measurer
   1446                 inlineObject.inlineXEnd = ReverseScaleDpi(measurer.cursorX)
   1447 
   1448     @staticmethod
   1449     def GetLinkHandlerClass():
   1450         return Link
   1451 
   1452     def GetLinkHandler(self):
   1453         handlerClass = self.GetLinkHandlerClass()
   1454         return handlerClass()
   1455 
   1456     def GetMenu(self):
   1457         m = []
   1458         if self._mouseOverUrl:
   1459             m = self.GetLinkHandler().GetLinkMenu(self, self._mouseOverUrl)
   1460         m += [(MenuLabel('/Carbon/UI/Controls/Common/Copy'), self.CopyText)]
   1461         if localization.UseImportantTooltip():
   1462             if self.hint:
   1463                 m += [(localization.GetByLabel('UI/Common/CopyTooltip'), self.CopyHint, (self.hint,))]
   1464         return m
   1465 
   1466     def GetAuxiliaryMenuOptions(self):
   1467         m = []
   1468         if self._offer_auxiliary_copy_option:
   1469             m.append((MenuLabel('/Carbon/UI/Controls/Common/Copy'), self.CopyText))
   1470             if localization.UseImportantTooltip():
   1471                 locTooltip = self.GetAuxiliaryTooltip()
   1472                 if locTooltip:
   1473                     m += [(localization.GetByLabel('UI/Common/CopyTooltip'), self.CopyHint, (locTooltip,))]
   1474         return m
   1475 
   1476     def GetAuxiliaryTooltip(self):
   1477         if self._inlineObjects:
   1478             mouseX = uicore.uilib.x
   1479             mouseY = uicore.uilib.y
   1480             left, top, width, height = self.GetAbsolute()
   1481             for inline in self._inlineObjects:
   1482                 startX = inline.inlineX
   1483                 endX = inline.inlineXEnd
   1484                 startY = inline.inlineY
   1485                 endY = startY + inline.inlineHeight
   1486                 if left + startX < mouseX < left + endX and top + startY < mouseY < top + endY:
   1487                     if inline.inlineType == 'localized':
   1488                         self.auxTooltipPosition = (left + startX,
   1489                          top + startY,
   1490                          endX - startX,
   1491                          inline.inlineHeight)
   1492                         return inline.data
   1493 
   1494         self.auxTooltipPosition = None
   1495         if self.autoFadeSides and self._alphaFadeRight:
   1496             return self.text
   1497 
   1498     def GetAuxiliaryTooltipPosition(self):
   1499         return self.auxTooltipPosition
   1500 
   1501     def GetHint(self):
   1502         baseHint = self.hint
   1503         if baseHint:
   1504             return baseHint
   1505         if self.autoFadeSides and self._alphaFadeRight:
   1506             return self.text
   1507 
   1508     def CopyText(self):
   1509         text = StripTags(self.text, stripOnly=['localized'])
   1510         blue.pyos.SetClipboardData(text)
   1511 
   1512     def CopyHint(self, hint):
   1513         if isinstance(hint, basestring):
   1514             blue.pyos.SetClipboardData(hint)
   1515 
   1516     def OnMouseEnter(self, *args):
   1517         self.CheckInlines()
   1518 
   1519     def OnMouseExit(self, *args):
   1520         self.CheckInlines()
   1521 
   1522     def OnMouseHover(self, *args):
   1523         self.CheckInlines()
   1524 
   1525     def CheckInlines(self):
   1526         inlineLinkObj = None
   1527         inlineHintObj = None
   1528         inlineLocalizationHintObj = None
   1529         if self._inlineObjects and uicore.uilib.mouseOver is self:
   1530             mouseX = uicore.uilib.x
   1531             mouseY = uicore.uilib.y
   1532             left, top, width, height = self.GetAbsolute()
   1533             for inline in self._inlineObjects:
   1534                 startX = inline.inlineX
   1535                 endX = inline.inlineXEnd
   1536                 startY = inline.inlineY
   1537                 endY = startY + inline.inlineHeight
   1538                 if left + startX < mouseX < left + endX and top + startY < mouseY < top + endY:
   1539                     if inline.inlineType == 'link':
   1540                         inlineLinkObj = inline
   1541                     elif inline.inlineType == 'hint':
   1542                         inlineHintObj = inline
   1543                     elif inline.inlineType == 'localized' and isinstance(inline.data, basestring):
   1544                         inlineLocalizationHintObj = inline
   1545                     elif inline.inlineType == 'words':
   1546                         self.MouseOverWordCallback(word=inline.data)
   1547 
   1548         self._resolvingInlineHint = True
   1549         mouseOverUrl = None
   1550         mouseOverUrlID = None
   1551         mouseOverTextBuff = None
   1552         inlineHint = None
   1553         if inlineLinkObj:
   1554             mouseOverUrl = inlineLinkObj.url
   1555             mouseOverUrlID = inlineLinkObj.urlID
   1556             mouseOverTextBuff = inlineLinkObj.textBuff
   1557             if inlineLinkObj.alt:
   1558                 inlineHint = inlineLinkObj.alt
   1559             else:
   1560                 standardHint = self.GetLinkHandler().GetStandardLinkHint(mouseOverUrl)
   1561                 if standardHint:
   1562                     inlineHint = standardHint
   1563             if inlineLinkObj.extraAlt:
   1564                 if inlineHint:
   1565                     inlineHint = inlineHint + '<br>' + inlineLinkObj.extraAlt
   1566                 else:
   1567                     inlineHint = inlineLinkObj.extraAlt
   1568             x, y = self.GetAbsolutePosition()
   1569             self.tooltipPosition = (x + inlineLinkObj.inlineX,
   1570              y + inlineLinkObj.inlineY,
   1571              inlineLinkObj.inlineXEnd - inlineLinkObj.inlineX,
   1572              inlineLinkObj.inlineHeight)
   1573         elif inlineHintObj:
   1574             x, y = self.GetAbsolutePosition()
   1575             self.tooltipPosition = (x + inlineHintObj.inlineX,
   1576              y + inlineHintObj.inlineY,
   1577              inlineHintObj.inlineXEnd - inlineHintObj.inlineX,
   1578              inlineHintObj.inlineHeight)
   1579         else:
   1580             self.tooltipPosition = None
   1581         if not inlineHint and inlineHintObj:
   1582             inlineHint = inlineHintObj.data
   1583         if inlineLocalizationHintObj:
   1584             if inlineHint:
   1585                 inlineHint += '<br>' + inlineLocalizationHintObj.data
   1586             else:
   1587                 inlineHint = inlineLocalizationHintObj.data
   1588         hint = inlineHint or getattr(self, '_objectHint', None)
   1589         if hint != self.hint:
   1590             self.hint = hint
   1591         self._resolvingInlineHint = False
   1592         if mouseOverUrl != self._mouseOverUrl:
   1593             if mouseOverUrl:
   1594                 PlaySound(uiconst.SOUND_BUTTON_HOVER)
   1595             self._mouseOverUrl = mouseOverUrl
   1596             self._mouseOverUrlID = mouseOverUrlID
   1597             self._mouseOverTextBuff = mouseOverTextBuff
   1598             self.Layout()
   1599             if mouseOverUrl:
   1600                 self._hiliteResetTimer = AutoTimer(50, self._ResetInlineHilite)
   1601 
   1602     def _ResetInlineHilite(self):
   1603         if uicore.uilib.mouseOver is self:
   1604             return
   1605         self._hiliteResetTimer = None
   1606         self.CheckInlines()
   1607 
   1608     def OnClick(self, *args):
   1609         if self._mouseOverUrl:
   1610             PlaySound(uiconst.SOUND_BUTTON_CLICK)
   1611             self.GetLinkHandler().ClickLink(self, self._mouseOverUrl.replace('&amp;', '&'))
   1612 
   1613     def OnMouseDown(self, *args):
   1614         if self._mouseOverUrl:
   1615             self.OnMouseDownWithUrl(self._mouseOverUrl, *args)
   1616         if getattr(self, '_mouseOverTextBuff', None) and self._mouseOverUrl:
   1617             self._dragLinkData = (''.join(self._mouseOverTextBuff), self._mouseOverUrl)
   1618         else:
   1619             self._dragLinkData = None
   1620         VisibleBase.OnMouseDown(self, *args)
   1621 
   1622     def OnMouseDownWithUrl(self, url, *args):
   1623         pass
   1624 
   1625     def OnMouseMove(self, *args):
   1626         if not self._hilite:
   1627             self.CheckInlines()
   1628         VisibleBase.OnMouseMove(self, *args)
   1629 
   1630     def GetDragData(self, *args):
   1631         if getattr(self, '_dragLinkData', None):
   1632             displayText, url = getattr(self, '_dragLinkData', None)
   1633             entry = Bunch()
   1634             entry.__guid__ = 'TextLink'
   1635             entry.url = url
   1636             entry.displayText = StripTags(displayText)
   1637             return [entry]
   1638 
   1639     def MouseOverWordCallback(self, word, *args):
   1640         if getattr(self, 'mouseOverWordCallback', None):
   1641             self.mouseOverWordCallback(word)
   1642 
   1643     @classmethod
   1644     def PrepareDrag(cls, *args):
   1645         return cls.GetLinkHandlerClass().PrepareDrag(*args)
   1646 
   1647     def UpdateAlignment(self, budgetLeft = 0, budgetTop = 0, budgetWidth = 0, budgetHeight = 0, updateChildrenOnly = False):
   1648         preWidth = self.displayWidth
   1649         preHeight = self.displayHeight
   1650         retBudgetLeft, retBudgetTop, retBudgetWidth, retBudgetHeight, sizeChanged = VisibleBase.UpdateAlignment(self, budgetLeft, budgetTop, budgetWidth, budgetHeight)
   1651         if not self._resolvingAutoSizing and self.isAffectedByPushAlignment and sizeChanged:
   1652             self.Layout('UpdateAlignment', absSize=(ReverseScaleDpi(self.displayWidth), ReverseScaleDpi(self.displayHeight)))
   1653             if preWidth != self.displayWidth or preHeight != self.displayHeight:
   1654                 retBudgetLeft, retBudgetTop, retBudgetWidth, retBudgetHeight, _sizeChanged = VisibleBase.UpdateAlignment(self, budgetLeft, budgetTop, budgetWidth, budgetHeight)
   1655         if sizeChanged and getattr(self, 'OnSizeChanged', None):
   1656             self.OnSizeChanged()
   1657         if self.autoFadeSides and self.parent:
   1658             length = self.autoFadeSides
   1659             if self.displayX + self.displayWidth > self.parent.displayWidth:
   1660                 fadeEnd = self.parent.displayWidth - self.displayX
   1661                 self.SetRightAlphaFade(uicore.ReverseScaleDpi(fadeEnd), length)
   1662                 self.renderObject.hasAuxiliaryTooltip = True
   1663             else:
   1664                 self.SetRightAlphaFade()
   1665         return (retBudgetLeft,
   1666          retBudgetTop,
   1667          retBudgetWidth,
   1668          retBudgetHeight,
   1669          sizeChanged)
   1670 
   1671     def GetTagStringValue(self, tagtofind, tagstring):
   1672         start = tagstring.find(tagtofind)
   1673         if start != -1:
   1674             tagBegin = tagstring[start + len(tagtofind):]
   1675             for checkQuote in ['"', "'"]:
   1676                 if tagBegin.startswith(checkQuote):
   1677                     end = tagBegin.find(checkQuote, 1)
   1678                     if end != -1:
   1679                         return tagBegin[1:end]
   1680 
   1681     def GetTagValue(self, tagtofind, tagstring):
   1682         start = tagstring.find(tagtofind)
   1683         if start != -1:
   1684             end = tagstring.find(' ', start)
   1685             if end == start:
   1686                 end = tagstring.find(' ', start + 1)
   1687             if end == -1:
   1688                 end = tagstring.find('>', start)
   1689             if end == -1:
   1690                 end = len(tagstring)
   1691             return tagstring[start + len(tagtofind):end]
   1692 
   1693     @classmethod
   1694     def MeasureTextSize(cls, text, **customAttributes):
   1695         customAttributes['text'] = text
   1696         customAttributes['parent'] = None
   1697         customAttributes['measuringText'] = True
   1698         customAttributes['align'] = uiconst.TOPLEFT
   1699         label = cls(**customAttributes)
   1700         return (label.textwidth, label.textheight)
   1701 
   1702     def OnGlobalFontSizeChanged(self):
   1703         if self._raw_fontsize is not None:
   1704             self.fontsize = self._raw_fontsize
   1705 
   1706     def OnGlobalFontShadowChanged(self):
   1707         if self._using_default_shadow:
   1708             self.shadowOffset = self.get_default_shadow_offset()
   1709 
   1710 
   1711 class LabelOverride(LabelCore):
   1712 
   1713     @classmethod
   1714     def __reload_update__(cls, old_cls):
   1715         return old_cls