infoBubble.py (23332B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\shared\shipTree\infoBubble.py 2 import log 3 import carbonui.const as uiconst 4 import eveicon 5 import evelink.client 6 import evetypes 7 import inventorycommon.typeHelpers 8 from carbon.client.script.environment.AudioUtil import PlaySound 9 from carbon.common.script.util.format import IntToRoman 10 from carbonui import Density, TextColor, PickState 11 from carbonui.text.styles import TextBody 12 from carbonui.button.group import ButtonGroup 13 from carbonui.control.button import Button 14 from carbonui.control.buttonIcon import ButtonIcon 15 from carbonui.primitives.container import Container 16 from carbonui.primitives.containerAutoSize import ContainerAutoSize 17 from carbonui.primitives.fill import Fill 18 from carbonui.primitives.frame import Frame 19 from carbonui.primitives.layoutGrid import LayoutGrid, LayoutGridRow 20 from carbonui.primitives.sprite import Sprite 21 from carbonui.uicore import uicore 22 from carbonui.util.color import Color 23 from characterdata.factions import get_faction_name 24 from eve.client.script.ui import eveColor 25 from eve.client.script.ui.control.eveLabel import EveCaptionMedium, EveLabelMedium, Label 26 from eve.client.script.ui.control.skillBar.skillLevel import SkillLevel 27 from eve.client.script.ui.shared.cloneGrade import ORIGIN_SHIPTREE 28 from eve.client.script.ui.shared.tooltip.itemObject import ItemObject 29 from eve.client.script.ui.shared.tooltip.skill_requirement import AddTrainingTime, AddSkillActionForList 30 from eve.client.script.ui.control.eveWindowUnderlay import ListEntryUnderlay 31 from eve.client.script.ui.control.itemIcon import ItemIcon 32 from eve.client.script.ui.control.pointerPanel import FrameWithPointer 33 from eve.client.script.ui.control.skillBar.skillBar import SkillBar 34 from eve.client.script.ui.shared.neocom.skillConst import COLOR_SKILL_1 35 from eve.client.script.ui.shared.shipTree.shipTreeConst import COLOR_TEXT 36 from eve.client.script.ui.shared.traits import TraitsContainer, TraitAttributeIcon 37 from eve.common.script.util.eveFormat import FmtISKAndRound 38 from eveservices.menu import GetMenuService 39 import expertSystems.client 40 from localization import GetByLabel, GetByMessageID 41 from omega.client.clone_grade_svc import get_clone_grade_svc 42 from utillib import KeyVal 43 44 class InfoBubble(Container): 45 default_name = 'InfoBubble' 46 default_align = uiconst.TOPLEFT 47 default_state = uiconst.UI_NORMAL 48 default_width = 350 49 default_height = 200 50 default_idx = 0 51 default_topOffset = 0 52 default_opacity = 0.0 53 54 def __init__(self, **kw): 55 Container.__init__(self, **kw) 56 self.StartupInfoBubble() 57 58 def ApplyAttributes(self, attributes): 59 Container.ApplyAttributes(self, attributes) 60 self.parentObj = attributes.parentObj 61 self.topOffset = attributes.Get('topOffset', self.default_topOffset) 62 self.__isClosing = False 63 self.mainCont = Container(name='mainCont', parent=self, padding=20) 64 self.bgFrame = Container(name='bgFrame', parent=self, state=uiconst.UI_DISABLED) 65 self.pointerFrame = FrameWithPointer(name='pointerFrame', parent=self.bgFrame, align=uiconst.TOALL) 66 self.topContainer = ContainerAutoSize(name='topContainer', parent=self.mainCont, align=uiconst.TOTOP, callback=self.OnMainContentSizeChanged) 67 self.iconCont = Container(name='iconCont', parent=self.topContainer, align=uiconst.TOPLEFT, width=80, height=80, left=0, top=0) 68 self.topRightCont = ContainerAutoSize(name='topRightCont', align=uiconst.TOPLEFT, left=90, width=self.width - 115, parent=self.topContainer, alignMode=uiconst.TOTOP) 69 self.caption = EveCaptionMedium(parent=self.topRightCont, align=uiconst.TOTOP) 70 self.attributeCont = ContainerAutoSize(name='attributeCont', parent=self.topRightCont, align=uiconst.TOTOP, padTop=5) 71 self.mainContent = ContainerAutoSize(name='mainContent', parent=self.mainCont, align=uiconst.TOTOP, callback=self.OnMainContentSizeChanged, padTop=5) 72 73 def StartupInfoBubble(self): 74 self.mainContent.SetSizeAutomatically() 75 self.OnMainContentSizeChanged() 76 self.SetLeftAndTop() 77 78 def SetLeftAndTop(self): 79 left, top, width, height = self.parentObj.GetAbsolute() 80 if left > self.width: 81 self.left = left - self.width - 20 82 self.pointerFrame.UpdatePointerPosition(uiconst.POINT_RIGHT_2) 83 else: 84 self.left = left + width + 20 85 self.pointerFrame.UpdatePointerPosition(uiconst.POINT_LEFT_2) 86 self.top = top - (self.height - height) / 2 87 88 def OnMainContentSizeChanged(self, *args): 89 self.height = self.topContainer.height + self.mainContent.height + 45 90 self.SetLeftAndTop() 91 92 def AnimShow(self): 93 uicore.animations.FadeIn(self, duration=0.15) 94 for i, attrIcon in enumerate(self.attributeCont.children): 95 timeOffset = i * 0.05 96 uicore.animations.FadeTo(attrIcon, 0.0, 1.0, duration=0.3, timeOffset=timeOffset) 97 uicore.animations.MorphScalar(attrIcon, 'left', attrIcon.left + 5, attrIcon.left, duration=0.1, timeOffset=timeOffset) 98 99 def Close(self): 100 if self.__isClosing: 101 return 102 self.__isClosing = True 103 uicore.animations.FadeOut(self, duration=0.15, callback=self._Close) 104 105 def _Close(self): 106 Container.Close(self) 107 108 def ConstructElements(self, attributeIDsDict): 109 attributeIDs = [ x[1] for x in sorted(attributeIDsDict.items(), key=lambda data: int(data[0])) ] 110 numInRow = 7 111 size = InfoBubbleAttributeIcon.default_width 112 for i, attributeID in enumerate(attributeIDs): 113 left = i % numInRow * (size + 1) 114 top = i / numInRow * (size + 1) 115 InfoBubbleAttributeIcon(parent=self.attributeCont, align=uiconst.TOPLEFT, attributeID=attributeID, left=left, top=top) 116 117 118 class InfoBubbleShipGroup(InfoBubble): 119 default_name = 'InfoBubbleShipGroup' 120 default_topOffset = 0 121 122 def ApplyAttributes(self, attributes): 123 self.default_width = 420 124 InfoBubble.ApplyAttributes(self, attributes) 125 self.node = attributes.node 126 factionName = '' 127 factionID = attributes.get('factionID', None) 128 if factionID: 129 factionName = get_faction_name(factionID) 130 infoBubbleGroup = cfg.infoBubbleGroups[self.node.shipGroupID] 131 self.caption.text = GetByMessageID(infoBubbleGroup['nameID']) 132 Frame(bgParent=self.iconCont, texturePath='res:/UI/Texture/Classes/ShipTree/Groups/groupIconFrame.png', opacity=0.15) 133 self.icon = Sprite(name='icon', parent=self.iconCont, pos=(0, 0, 64, 64), align=uiconst.CENTER, texturePath=infoBubbleGroup['iconLarge']) 134 self.ConstructElements(infoBubbleGroup['elements']) 135 Label(parent=self.mainContent, align=uiconst.TOTOP, text=GetByMessageID(infoBubbleGroup['descriptionID']), padTop=5) 136 skillSvc = sm.GetService('skills') 137 if self.node.IsLockedWithoutExpertSystem(): 138 layoutGrid = LayoutGrid(parent=self.mainContent, align=uiconst.TOTOP, padTop=12) 139 caption = InfoBubbleCaption(align=uiconst.TOPLEFT, text=GetByLabel('UI/ShipTree/SkillsRequiredToUnlock'), padding=(0, 12, 0, 2), width=self.width - 40) 140 requiredSkills = self.node.GetRequiredSkillsSorted() 141 isOmegaRestricted = False 142 layoutGrid.AddCell(caption, colSpan=2) 143 for typeID, level in requiredSkills: 144 if not isOmegaRestricted: 145 isOmegaRestricted = get_clone_grade_svc().is_requirements_restricted(typeID) 146 if not isOmegaRestricted: 147 isOmegaRestricted = get_clone_grade_svc().is_skill_level_restricted(typeID, level) 148 layoutGrid.AddRow(rowClass=SkillEntry, typeID=typeID, level=level) 149 150 isOmega = get_clone_grade_svc().is_omega_with_grace_period() 151 trainingTime = self.node.GetTimeToUnlock() 152 AddTrainingTime(layoutGrid, isOmega=isOmega, isOmegaRestricted=isOmegaRestricted, trainingTime=trainingTime, activityText=GetByLabel('Tooltips/SkillPlanner/ActivityUnlockShipGroup'), minWidth=370) 153 isOmegaLocked = isOmegaRestricted and not isOmega 154 if not isOmegaLocked: 155 skillPlanName = '%s %s' % (factionName, GetByMessageID(infoBubbleGroup['nameID'])) 156 allRequiredSkills = skillSvc.GetSkillsMissingToUseAllSkillsFromListRecursiveAsList(requiredSkills) 157 missingSkills = skillSvc.GetMissingSkillBooksFromList(allRequiredSkills) 158 AddSkillActionForList(layoutGrid, allRequiredSkills, missingSkills, skillPlanName=skillPlanName, on_click_callback=self.Close) 159 else: 160 bonusSkills = self.node.GetBonusSkillsSorted() 161 if bonusSkills: 162 layoutGrid = LayoutGrid(parent=self.mainContent, align=uiconst.TOTOP) 163 caption = InfoBubbleCaption(align=uiconst.TOPLEFT, text=GetByLabel('UI/ShipTree/ShipBonusSkills'), padding=(0, 12, 0, 2), width=self.width - 40) 164 layoutGrid.AddCell(caption, colSpan=2) 165 indentForButton = False 166 for typeID, level in bonusSkills: 167 if skillSvc.GetSkill(typeID).trainedSkillLevel < 5: 168 indentForButton = True 169 170 for typeID, level in bonusSkills: 171 showButton = indentForButton 172 if showButton and skillSvc.GetSkill(typeID).trainedSkillLevel >= 5: 173 showButton = False 174 layoutGrid.AddRow(rowClass=SkillEntry, typeID=typeID, level=level, showLevel=False, showButton=showButton, indentForButton=indentForButton) 175 176 layoutGrid.AddRow(rowClass=BonusSkillInfoRow, plural=len(bonusSkills) > 1) 177 self.AnimShow() 178 179 180 class BonusSkillInfoRow(LayoutGridRow): 181 default_name = 'BonuSkillInfoRow' 182 183 def ApplyAttributes(self, attributes): 184 super(BonusSkillInfoRow, self).ApplyAttributes(attributes) 185 plural = attributes.get('plural', False) 186 mainCont = ContainerAutoSize(align=uiconst.TOTOP, padding=(5, 10, 5, 5)) 187 icon = Sprite(parent=mainCont, align=uiconst.CENTERLEFT, color=eveColor.AURA_PURPLE, texturePath='res:/UI/Texture/classes/careerPortal/aura/aura_icon_16x16.png', width=16, height=16, left=8) 188 if plural: 189 labelText = GetByLabel('UI/ShipTree/ShipBonusSkillsInfoPlural') 190 else: 191 labelText = GetByLabel('UI/ShipTree/ShipBonusSkillsInfoSingular') 192 label = TextBody(parent=mainCont, align=uiconst.CENTERLEFT, width=280, text=labelText, color=eveColor.AURA_PURPLE, left=35) 193 Container(bgParent=mainCont, bgColor=eveColor.AURA_PURPLE, opacity=0.2, padding=-5) 194 self.AddCell(mainCont, colSpan=self.columns) 195 196 197 class AddSkillToQueueButton(Button): 198 199 def ApplyAttributes(self, attributes): 200 self.default_func = self.ClickFunc 201 super(AddSkillToQueueButton, self).ApplyAttributes(attributes) 202 self._callback = attributes.get('callback', None) 203 self.skillID = attributes.skillID 204 205 def ClickFunc(self, *args): 206 skillQueueSvc = sm.GetService('skillqueue') 207 skillQueueSvc.AddSkillsToQueue(skills=[(self.skillID, None)], ignoreAlreadyPresent=True) 208 self.UpdateState() 209 if self._callback: 210 self._callback() 211 212 def UpdateState(self): 213 skillQueueSvc = sm.GetService('skillqueue') 214 skillLevelInQueue = skillQueueSvc.GetSkillLevelsInQueue().get(self.skillID, 0) 215 if skillLevelInQueue >= 5: 216 self.Disable() 217 218 219 class SkillEntry(LayoutGridRow): 220 default_name = 'SkillEntryLayoutGridRow' 221 default_state = uiconst.UI_NORMAL 222 default_showLevel = True 223 isDragObject = True 224 default_skillBarPadding = (24, 8, 8, 8) 225 default_textPadding = (0, 3, 0, 3) 226 227 def ApplyAttributes(self, attributes): 228 LayoutGridRow.ApplyAttributes(self, attributes) 229 self.typeID = attributes.typeID 230 self.level = attributes.level 231 self.showLevel = attributes.Get('showLevel', self.default_showLevel) 232 self.skillBarPadding = attributes.Get('skillBarPadding', self.default_skillBarPadding) 233 self.textPadding = attributes.Get('textPadding', self.default_textPadding) 234 self.indentForButton = attributes.Get('indentForButton', False) 235 self.showTrainButton = attributes.Get('showButton', False) 236 self.addToQueueButton = None 237 self._hiliteFill = None 238 leftCont = ContainerAutoSize(padding=self.textPadding, align=uiconst.CENTERLEFT) 239 self.label = Label(name='label', parent=leftCont, align=uiconst.CENTERLEFT) 240 self.AddCell(leftCont, cellPadding=self.cellPadding) 241 skillQueueSvc = sm.GetService('skillqueue') 242 skillLevelInQueue = skillQueueSvc.GetSkillLevelsInQueue().get(self.typeID, 0) 243 if skillLevelInQueue >= 5: 244 self.showTrainButton = self.indentForButton = False 245 skillBarPadding = (0, 0, 0, 0) if self.showTrainButton else self.textPadding 246 skillBarContainer = ContainerAutoSize(name='skillBarCont', padding=skillBarPadding, align=uiconst.CENTERRIGHT) 247 self.AddCell(skillBarContainer, cellPadding=self.skillBarPadding) 248 self.buttonContainer = Container(parent=skillBarContainer, align=uiconst.CENTERRIGHT, height=18, width=20) 249 self.omegaIconButton = ButtonIcon(name='omegaButton', parent=self.buttonContainer, align=uiconst.CENTER, density=Density.COMPACT, texturePath=eveicon.omega, iconSize=16, func=self._OnOmegaButton) 250 self.addToQueueButton = AddSkillToQueueButton(name='addToQButton', skillID=self.typeID, parent=self.buttonContainer, align=uiconst.CENTER, density=Density.COMPACT, texturePath=eveicon.add, callback=self.UpdateState) 251 skillBarIndent = 30 if self.indentForButton else 0 252 self.skillBar = SkillBar(parent=skillBarContainer, align=uiconst.CENTERRIGHT, state=uiconst.UI_DISABLED, skillID=self.typeID, requiredLevel=self.level if self.showLevel else None, pos=(skillBarIndent, 253 0, 254 SkillBar.default_width, 255 SkillBar.default_height)) 256 self.icon = Sprite(name='icon', parent=skillBarContainer, align=uiconst.CENTERRIGHT, pos=(self.skillBar.width + 5, 257 0, 258 16, 259 16), state=uiconst.UI_NORMAL if self.showLevel else uiconst.UI_HIDDEN) 260 self.UpdateState() 261 262 def GetMyLevel(self): 263 return sm.GetService('skills').GetMyLevelIncludingLapsed(self.typeID) 264 265 def UpdateState(self): 266 myLevel = self.GetMyLevel() 267 if self.showTrainButton: 268 self.buttonContainer.Show() 269 skillQueueSvc = sm.GetService('skillqueue') 270 skillLevelInQueue = skillQueueSvc.GetSkillLevelsInQueue().get(self.typeID, 0) 271 if not get_clone_grade_svc().is_omega_with_grace_period() and get_clone_grade_svc().is_skill_level_restricted(self.typeID, max(myLevel + 1, skillLevelInQueue + 1)): 272 self.omegaIconButton.Show() 273 self.addToQueueButton.Hide() 274 else: 275 self.omegaIconButton.Hide() 276 self.addToQueueButton.Show() 277 else: 278 self.buttonContainer.Hide() 279 if myLevel and myLevel >= 5: 280 if self.addToQueueButton: 281 self.addToQueueButton.Hide() 282 if self.skillBar: 283 self.skillBar.Update() 284 if self.showLevel: 285 if myLevel and self.level <= myLevel: 286 color = TextColor.SECONDARY 287 levelColor = TextColor.SECONDARY 288 else: 289 color = TextColor.NORMAL 290 levelColor = TextColor.NORMAL 291 text = GetByLabel('UI/InfoWindow/RequiredSkillNameAndLevel', skill=self.typeID, level=IntToRoman(self.level), levelColor=Color(*levelColor).GetHex()) 292 self.label.SetRGBA(*color) 293 self.label.left = 10 294 self.icon.state = uiconst.UI_NORMAL 295 if myLevel and myLevel >= self.level: 296 self.icon.SetTexturePath('res:/ui/Texture/classes/Skills/SkillRequirementMet.png') 297 self.icon.hint = GetByLabel('UI/InfoWindow/SkillTrainedToRequiredLevel') 298 self.icon.SetRGBA(1.0, 1.0, 1.0, 0.5) 299 else: 300 self.icon.SetRGBA(*TextColor.WARNING) 301 if not sm.GetService('skills').HasSkill(self.typeID): 302 self.icon.SetTexturePath(eveicon.skill_book) 303 self.icon.hint = GetByLabel('UI/SkillQueue/SkillBookMissing') 304 else: 305 self.icon.SetTexturePath('res:/ui/Texture/classes/Skills/SkillRequirementNotMet.png') 306 self.icon.hint = GetByLabel('UI/InfoWindow/SkillNotTrainedToRequiredLevel') 307 else: 308 text = evetypes.GetName(self.typeID) 309 self.label.SetRGBA(*TextColor.NORMAL) 310 self.label.left = 10 311 self.icon.state = uiconst.UI_HIDDEN 312 self.label.SetText(text) 313 314 def GetMenu(self): 315 return GetMenuService().GetMenuForSkill(self.typeID) 316 317 def OnClick(self): 318 PlaySound(uiconst.SOUND_BUTTON_CLICK) 319 sm.GetService('info').ShowInfo(self.typeID) 320 321 def OnMouseEnter(self, *args): 322 self.ShowHilite() 323 PlaySound(uiconst.SOUND_BUTTON_HOVER) 324 325 def GetDragData(self): 326 ret = KeyVal(__guid__='uicls.GenericDraggableForTypeID', typeID=self.typeID, label=evetypes.GetName(self.typeID)) 327 return (ret,) 328 329 def ConstructHiliteFill(self): 330 if not self._hiliteFill: 331 self._hiliteFill = ListEntryUnderlay(bgParent=self) 332 333 def ShowHilite(self, animate = True): 334 self.ConstructHiliteFill() 335 self._hiliteFill.set_hovered(True, animate) 336 337 def HideHilite(self, animate = True): 338 self.ConstructHiliteFill() 339 self._hiliteFill.set_hovered(False, animate) 340 341 def OnMouseExit(self, *args): 342 self.HideHilite() 343 344 def _OnOmegaButton(self, *args): 345 PlaySound(uiconst.SOUND_BUTTON_CLICK) 346 uicore.cmd.OpenCloneUpgradeWindow(ORIGIN_SHIPTREE, self.typeID) 347 348 349 class ExpertSystemIcon(Sprite): 350 default_state = uiconst.UI_NORMAL 351 352 def ApplyAttributes(self, attributes): 353 self.infoSvc = sm.GetService('info') 354 Sprite.ApplyAttributes(self, attributes) 355 self.shipID = attributes.get('shipID', None) 356 357 def OnClick(self, *args): 358 from eve.client.script.ui.shared.neocom.characterSheetWindow import CharacterSheetWindow 359 CharacterSheetWindow.OpenExpertSystems() 360 361 362 class ExpertSystemInfoRow(ContainerAutoSize): 363 default_alignMode = uiconst.TOPLEFT 364 default_padding = 10 365 366 def ApplyAttributes(self, attributes): 367 ContainerAutoSize.ApplyAttributes(self, attributes) 368 expertSystemTypeID = attributes.expertSystemTypeID 369 link = evelink.type_link(expertSystemTypeID) 370 EveLabelMedium(parent=self, align=uiconst.TOPLEFT, text=link, maxWidth=150, state=uiconst.UI_NORMAL) 371 expertSystems.ViewExpertSystemInStoreButton(parent=self, align=uiconst.CENTERRIGHT, left=20, expert_system_type_id=expertSystemTypeID) 372 373 374 class InfoBubbleShip(InfoBubble): 375 default_name = 'InfoBubbleShip' 376 default_topOffset = 20 377 378 def ApplyAttributes(self, attributes): 379 InfoBubble.ApplyAttributes(self, attributes) 380 self.typeID = attributes.typeID 381 self.expertSystems = attributes.expertSystems 382 self.caption.text = evetypes.GetName(self.typeID) 383 self.icon = ItemIcon(name='icon', parent=self.iconCont, pos=(0, 0, 80, 80), typeID=self.typeID, cursor=uiconst.UICURSOR_MAGNIFIER) 384 self.icon.OnClick = self._OnIconClick 385 if self.typeID in cfg.infoBubbleTypeElements: 386 self.ConstructElements(cfg.infoBubbleTypeElements[self.typeID]) 387 try: 388 TraitsContainer(parent=self.mainContent, typeID=self.typeID) 389 except Exception: 390 log.LogException() 391 self.Close() 392 return 393 394 if self.expertSystems: 395 expertSystemsInfoCont = ContainerAutoSize(parent=self.mainContent, align=uiconst.TOTOP, padTop=20) 396 expertSystemSpriteLabelCont = ContainerAutoSize(parent=expertSystemsInfoCont, align=uiconst.TOTOP, alignMode=uiconst.TOPLEFT) 397 expertSystemIconSize = 24 398 ExpertSystemIcon(parent=expertSystemSpriteLabelCont, align=uiconst.TOPLEFT, texturePath='res:/UI/Texture/classes/ExpertSYstems/logo_simple_24.png', width=expertSystemIconSize, height=expertSystemIconSize) 399 EveLabelMedium(text=GetByLabel('UI/ShipTree/ExpertSystemsCanHelpFlyShip'), align=uiconst.CENTERLEFT, parent=expertSystemSpriteLabelCont, padLeft=expertSystemIconSize + 4) 400 for esTypeID in self.expertSystems: 401 ExpertSystemInfoRow(parent=expertSystemsInfoCont, align=uiconst.TOTOP, expertSystemTypeID=esTypeID) 402 403 price = inventorycommon.typeHelpers.GetAveragePrice(self.typeID) or 0 404 text = GetByLabel('UI/Inventory/EstIskPrice', iskString=FmtISKAndRound(price, False)) 405 Label(parent=self.topRightCont, text=text, align=uiconst.TOTOP, padTop=7) 406 button_group = ButtonGroup(parent=self.mainContent, align=uiconst.TOTOP, padTop=16) 407 Button(parent=button_group, align=uiconst.CENTER, label=GetByLabel('UI/Compare/CompareButton'), func=self.compare_type) 408 uicore.animations.FadeTo(self.icon, 1.5, 1.0, duration=0.6) 409 self.AnimShow() 410 411 def compare_type(self, *args): 412 from eve.client.script.ui.comparison.comparisonWindow import ComparisonWindow 413 ComparisonWindow.open_and_compare(self.typeID) 414 415 def _AtLeastOneExpertSystemBenefitsPlayer(self, expertSystemIDs): 416 for expertSystemID in expertSystemIDs: 417 if expertSystems.expert_system_benefits_player(expertSystemID): 418 return True 419 420 return False 421 422 def AnimShow(self): 423 InfoBubble.AnimShow(self) 424 for i, obj in enumerate(self.mainContent.children): 425 timeOffset = i * 0.015 426 uicore.animations.FadeTo(obj, 0.0, 1.0, duration=0.3, timeOffset=timeOffset) 427 428 def SetLeftAndTop(self): 429 left, top, width, height = self.parentObj.GetAbsolute() 430 self.left = left - (self.width - width) / 2 431 if top > self.height: 432 self.top = top - self.height - self.topOffset 433 self.pointerFrame.UpdatePointerPosition(uiconst.POINT_BOTTOM_2) 434 else: 435 newTop = top + height + 2 + self.topOffset 436 if newTop + self.height <= uicore.desktop.height: 437 self.top = newTop 438 self.pointerFrame.UpdatePointerPosition(uiconst.POINT_TOP_2) 439 else: 440 self.top = 0 441 self.left = left + width + self.topOffset 442 if self.height < top + height: 443 self.pointerFrame.UpdatePointerPosition(uiconst.POINT_LEFT_3) 444 else: 445 self.pointerFrame.UpdatePointerPosition(uiconst.POINTER_NONE) 446 447 def _OnIconClick(self, *args, **kwargs): 448 self.Close() 449 sm.GetService('info').ShowInfo(self.typeID) 450 451 452 class InfoBubbleCaption(Label): 453 default_color = COLOR_TEXT 454 default_bold = True 455 default_padTop = 8 456 457 458 class InfoBubbleAttributeIcon(TraitAttributeIcon): 459 default_name = 'InfoBubbleAttributeIcon' 460 default_padRight = 1 461 default_opacity = 0.0