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

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