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('&', '&') 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('&', '&')) 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