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

selectedItemWnd.py (40361B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\inflight\selectedItemWnd.py
      2 import blue
      3 import eveformat.client
      4 import eveicon
      5 import evetypes
      6 import localization
      7 import telemetry
      8 import uthread
      9 import uthread2
     10 import utillib
     11 from brennivin import itertoolsext
     12 from carbon.client.script.environment.audioService import GetAudioService
     13 from carbon.common.script.util.format import FmtDist
     14 from carbon.common.script.util.timerstuff import AutoTimer
     15 from carbonui import TextColor, uiconst, Align
     16 from carbonui.control.buttonIcon import ButtonIcon
     17 from carbonui.control.contextMenu import menuDataFactory
     18 from carbonui.control.contextMenu.menuConst import DISABLED_ENTRY0
     19 from carbonui.control.contextMenu.menuData import MenuData
     20 from carbonui.control.contextMenu.menuEntryData import MenuEntryData
     21 from carbonui.control.dragdrop.dragdata import ShipDragData
     22 from carbonui.control.scrollContainer import ScrollContainer
     23 from carbonui.primitives.container import Container
     24 from carbonui.primitives.containerAutoSize import ContainerAutoSize
     25 from carbonui.uiconst import PickState
     26 from carbonui.window.header.small import SmallWindowHeader
     27 from carbonui.window.segment.underlay import WindowSegmentUnderlay
     28 from carbonui.window.widget import WidgetWindow
     29 from collections import OrderedDict
     30 from eve.client.script.parklife import states as state
     31 from eve.client.script.ui.control import eveIcon, eveLabel
     32 from eve.client.script.ui.inflight import actionPanelDefaults, selectedItemConst, actionConst
     33 from eve.client.script.ui.inflight.actionConst import CMD_BY_ACTIONID
     34 from eve.client.script.ui.inflight.selectedItemButton import SelectedItemButton
     35 from eve.client.script.ui.inflight.selectedItemConst import ACTIONS_ALWAYS_VISIBLE, ACTIONS_BY_CATEGORYID, ACTIONS_BY_GROUPID, ACTIONS_BY_TYPEID, SUB_MENU_ACTIONS, TOGGLE_ACTIONS
     36 from eve.client.script.ui.inflight.selectedItemTimers import HasSkyhookTimer, HasStructureTimer, GetTimerText
     37 from eve.client.script.ui.shared import killRights
     38 from eve.client.script.ui.shared.pointerTool import pointerToolConst
     39 from eve.client.script.ui.util import uix
     40 from eve.common.lib import appConst as const
     41 from eve.common.script.sys import idCheckers
     42 from eve.common.script.util import eveFormat
     43 from eveservices.menu import GetMenuService, StartMenuService
     44 from eveservices.xmppchat import GetChatService
     45 from localization import GetByLabel
     46 from menu import MenuLabel
     47 from npcs.client.components.entity_bounty import get_entity_bounty
     48 from shipcaster.client.shipcasterUtil import GetShipcasterText
     49 from spacecomponents.client.components import cargobay
     50 from spacecomponents.common.helper import HasCargoBayComponent, HasShipcasterComponent, HasStarterShipcasterComponent, HasUnderConstructionComponent, HasTowGameObjectiveComponent, HasFilamentSpoolupComponent
     51 from spacecomponents.common.helper import HasItemTrader
     52 from uihider import UiHiderMixin
     53 ITEM_ICON_SIZE_LARGE = 64
     54 ITEM_ICON_SIZE_SMALL = 32
     55 BUTTON_CONTAINER_MIN_WIDTH = 270
     56 BUTTON_ICON_LARGE = 32
     57 BUTTON_ICON_SMALL = 16
     58 BUTTON_SIZE_LARGE = BUTTON_ICON_LARGE
     59 BUTTON_SIZE_SMALL = BUTTON_ICON_SMALL + 4
     60 BUTTON_PADDING = 4
     61 TITLE_CHIP_PADDING = 8
     62 HEIGHTSTATE_SMALL = 1
     63 HEIGHTSTATE_MEDIUM = 2
     64 HEIGHTSTATE_LARGE = 3
     65 
     66 class CharIcon(Container):
     67     isDragObject = True
     68 
     69     def ApplyAttributes(self, attributes):
     70         super(CharIcon, self).ApplyAttributes(attributes)
     71         self.typeID = None
     72         self.itemID = None
     73         self.chatIcon = ButtonIcon(name='chatIcon', parent=self, align=Align.BOTTOMRIGHT, texturePath=eveicon.chat_bubble, pos=(0, 0, 16, 16), iconSize=16, func=self.OnChatButton, hint=GetByLabel('UI/Chat/StartConversation'))
     74         self.icon = eveIcon.Icon(name='iconSprite', align=Align.TOALL, pickState=PickState.OFF, parent=self)
     75 
     76     def OnChatButton(self, *args):
     77         GetChatService().Invite(self.itemID)
     78 
     79     def SetID(self, typeID, itemID):
     80         self.typeID = typeID
     81         self.itemID = itemID
     82         self.icon.LoadIconByTypeID(typeID, itemID=itemID, ignoreSize=True)
     83 
     84     def GetMenu(self):
     85         return sm.GetService('menu').GetMenuForOwner(self.itemID)
     86 
     87     def GetDragData(self, *args):
     88         charID = self.itemID
     89         if not charID:
     90             return []
     91         fakeNode = utillib.KeyVal()
     92         fakeNode.charID = charID
     93         fakeNode.typeID = self.typeID
     94         fakeNode.itemID = charID
     95         fakeNode.info = cfg.eveowners.Get(charID)
     96         fakeNode.__guid__ = 'listentry.User'
     97         return [fakeNode]
     98 
     99     def OnClick(self, *args):
    100         sm.GetService('info').ShowInfo(self.typeID, self.itemID)
    101 
    102 
    103 class SelectedItemWnd(UiHiderMixin, WidgetWindow):
    104     uniqueUiName = pointerToolConst.UNIQUE_NAME_SELECTED_ITEM_VIEW
    105     __notifyevents__ = ['ProcessSessionChange',
    106      'OnStateChange',
    107      'DoBallRemove',
    108      'OnDistSettingsChange',
    109      'OnPlanetViewChanged',
    110      'DoBallsRemove',
    111      'OnActiveTrackingChange']
    112     default_minSize = (256, 0)
    113     default_windowID = 'selecteditemview'
    114     default_open = True
    115     default_isKillable = False
    116     default_captionLabelPath = 'UI/Inflight/ActiveItem/SelectedItem'
    117     default_scope = uiconst.SCOPE_INFLIGHT
    118     default_apply_content_padding = False
    119 
    120     @staticmethod
    121     def default_left(*args):
    122         return actionPanelDefaults.GetActiveItemLeft()
    123 
    124     @staticmethod
    125     def default_top(*args):
    126         return actionPanelDefaults.GetActiveItemTop()
    127 
    128     @staticmethod
    129     def default_width():
    130         return actionPanelDefaults.GetActiveItemWidth()
    131 
    132     @staticmethod
    133     def default_height():
    134         return actionPanelDefaults.GetActiveItemHeight()
    135 
    136     def ApplyAttributes(self, attributes):
    137         super(SelectedItemWnd, self).ApplyAttributes(attributes)
    138         panelname = attributes.panelname
    139         self.inited = False
    140         self.canAnchorToOthers = 0
    141         self.lastActionSerial = None
    142         self.lastSessionChange = None
    143         self.lastIcon = None
    144         self.lastBountyCheck = None
    145         self.bountyText = None
    146         self.buttonCont = None
    147         self.updateAutoTimer = None
    148         self.itemID = None
    149         self.panelname = panelname
    150         self.lastActionDist = None
    151         self.lastCloakingStatus = False
    152         self.noContentHint = None
    153         self.bountySvc = sm.GetService('bountySvc')
    154         self._last_button_size = None
    155         self.titleCont = None
    156         self.character_title_chip = None
    157         self._update_extend_content_into_header()
    158         self.ConstructLayout()
    159         stateSvc = sm.GetServiceIfRunning('stateSvc')
    160         if stateSvc:
    161             self.itemID = stateSvc.GetExclState(state.selected)
    162         self.topCont._OnSizeChange_NoBlock = self._on_top_cont_size_changed
    163         self.on_compact_mode_changed.connect(self._on_compact_mode_changed)
    164         self.on_content_padding_changed.connect(self._on_content_padding_changed)
    165         self.on_header_inset_changed.connect(self._on_window_header_inset_changed)
    166         self.on_end_scale.connect(self._on_end_scale)
    167         self.inited = True
    168         uthread2.start_tasklet(self.UpdateAll)
    169 
    170     def _UpdateMinSize(self):
    171         top_height = self.nameRowCont.height + self.topCont.padTop + self.topCont.padBottom
    172         _, bottom_height = self.bottomCont.GetAbsoluteSize()
    173         content_height = top_height + bottom_height
    174         _, height_min = self.GetWindowSizeForContentSize(height=content_height)
    175         if self.compact:
    176             width_min = 200
    177         else:
    178             width_min = self.default_minSize[0]
    179         self.SetMinSize((width_min, height_min))
    180 
    181     def ConstructLayout(self):
    182         self._ConstructButtonCont()
    183         self.topCont = Container(name='topCont', parent=self.content, align=uiconst.TOALL, clipChildren=True)
    184         self._ConstructIconsAndLabel()
    185         self._update_padding_of_content()
    186         self.on_name_row_size_changed(*self.nameRowCont.GetAbsoluteSize())
    187 
    188     def _on_top_cont_size_changed(self, *args, **kwargs):
    189         self.ReconstructItemIcons()
    190         self.UpdateAll()
    191 
    192     def _on_end_scale(self, window):
    193         self._UpdateMinSize()
    194 
    195     def _ConstructIconsAndLabel(self):
    196         self.killRightsUtilMenuCont = Container(name='utilMenuCont', align=uiconst.TORIGHT, parent=self.topCont, width=16, state=uiconst.UI_HIDDEN, padLeft=4)
    197         self.killRightsUtilMenu = killRights.KillRightsUtilMenu(menuAlign=uiconst.TOPRIGHT, parent=self.killRightsUtilMenuCont, width=16, height=16, align=uiconst.TOPRIGHT)
    198         self.iconCont = ContainerAutoSize(name='iconCont', parent=self.topCont, align=uiconst.TOLEFT, padTop=2, padRight=8)
    199         self.ReconstructItemIcons()
    200         self.labelCont = ScrollContainer(name='labelCont', parent=self.topCont, state=uiconst.UI_PICKCHILDREN)
    201         self.labelCont.mainCont.state = uiconst.UI_PICKCHILDREN
    202         self.nameRowCont = NameRowContainer(name='nameRowCont', parent=self.labelCont, align=Align.TOTOP, clipChildren=True, height=24)
    203         self.nameLabel = eveLabel.EveLabelMedium(name='nameLabel', parent=self.nameRowCont, align=Align.TOLEFT, color=TextColor.HIGHLIGHT, default_maxLines=1, autoFadeSides=16, padTop=2)
    204         self.titleCont = ContainerAutoSize(name='titleCont', parent=self.nameRowCont, align=Align.TOLEFT, padLeft=TITLE_CHIP_PADDING)
    205         self.titleCont.on_size_changed.connect(self.on_title_size_changed)
    206         self.nameRowCont.on_size_changed.connect(self.on_name_row_size_changed)
    207         self.distanceLabel = eveLabel.EveLabelMedium(name='distanceLabel', parent=self.labelCont, align=uiconst.TOTOP, state=uiconst.UI_DISABLED, color=TextColor.HIGHLIGHT)
    208         self.distanceLabel.display = False
    209         self.securityLabel = eveLabel.EveLabelMedium(name='securityLabel', parent=self.labelCont, align=uiconst.TOTOP, state=uiconst.UI_DISABLED, color=TextColor.HIGHLIGHT)
    210         self.securityLabel.display = False
    211         self.timerLabel = eveLabel.EveLabelMedium(name='timerLabel', parent=self.labelCont, align=uiconst.TOTOP, state=uiconst.UI_DISABLED, color=TextColor.HIGHLIGHT)
    212         self.timerLabel.display = False
    213         self.bountyLabel = eveLabel.EveLabelMedium(name='bountyLabel', parent=self.labelCont, align=uiconst.TOTOP, state=uiconst.UI_DISABLED, color=TextColor.HIGHLIGHT)
    214         self.bountyLabel.display = False
    215 
    216     def on_title_size_changed(self, width, height):
    217         self.on_name_row_size_changed(*self.nameRowCont.GetAbsoluteSize())
    218 
    219     def on_name_row_size_changed(self, width, height):
    220         if self.character_title_chip is None:
    221             title_width = self.titleCont.width
    222         else:
    223             title_width = self.character_title_chip.measured_width
    224         if width < self.nameLabel.width + title_width + TITLE_CHIP_PADDING:
    225             self.titleCont.display = False
    226             self.nameRowCont.state = uiconst.UI_NORMAL
    227         else:
    228             self.titleCont.display = True
    229             self.nameRowCont.state = uiconst.UI_PICKCHILDREN
    230 
    231     def _OnResize(self, *args, **kw):
    232         self._UpdateButtonSize()
    233         super(SelectedItemWnd, self)._OnResize(*args, **kw)
    234 
    235     def _UpdateIconContVisibility(self):
    236         self.iconCont.SetSizeAutomatically()
    237         heightState = self.GetHeightState()
    238         if heightState == HEIGHTSTATE_SMALL:
    239             self.iconCont.Hide()
    240         else:
    241             self.iconCont.Show()
    242             iconSize = ITEM_ICON_SIZE_LARGE if heightState == HEIGHTSTATE_LARGE else ITEM_ICON_SIZE_SMALL
    243             self.iconSprite.SetSize(iconSize, iconSize)
    244             self.charIcon.SetSize(iconSize, iconSize)
    245             self.charIcon.left = iconSize + 4
    246 
    247     def GetHeightState(self):
    248         _, h = self.topCont.GetAbsoluteSize()
    249         if h > ITEM_ICON_SIZE_LARGE:
    250             return HEIGHTSTATE_LARGE
    251         elif h > ITEM_ICON_SIZE_SMALL:
    252             return HEIGHTSTATE_MEDIUM
    253         else:
    254             return HEIGHTSTATE_SMALL
    255 
    256     def _UpdateButtonSize(self):
    257         button_size = self.button_size
    258         icon_size = self.get_button_icon_size(button_size)
    259         self.buttonCont.height = button_size
    260         for button in self.buttonCont.children:
    261             self._ApplyButtonSize(button, icon_size, button_size)
    262 
    263         if self._last_button_size is None or button_size != self._last_button_size:
    264             self._last_button_size = button_size
    265             self._UpdateMinSize()
    266 
    267     def _ApplyButtonSize(self, button, iconSize, buttonSize):
    268         button.width = buttonSize
    269         button.SetIconSize(iconSize)
    270         button.SetTexturePath(actionConst.ICON_BY_ACTIONID[button.GetActionID()].resolve(iconSize))
    271 
    272     @property
    273     def button_size(self):
    274         total_width, _ = self.GetCurrentAbsoluteSize()
    275         if total_width >= self.get_min_button_container_width(BUTTON_SIZE_LARGE):
    276             return BUTTON_SIZE_LARGE
    277         else:
    278             return BUTTON_SIZE_SMALL
    279 
    280     def get_button_icon_size(self, button_size):
    281         if button_size == BUTTON_SIZE_LARGE:
    282             return BUTTON_ICON_LARGE
    283         else:
    284             return BUTTON_ICON_SMALL
    285 
    286     def get_min_button_container_width(self, button_size):
    287         container_padding = self.buttonCont.padLeft + self.buttonCont.padRight
    288         total_width = container_padding + len(self.buttonCont.children) * (button_size + BUTTON_PADDING)
    289         return max(total_width, BUTTON_CONTAINER_MIN_WIDTH)
    290 
    291     def _ConstructButtonCont(self):
    292         self.bottomCont = ContainerAutoSize(name='bottomCont', parent=self.content, align=uiconst.TOBOTTOM)
    293         self.buttonCont = Container(name='buttonCont', parent=self.bottomCont, align=uiconst.TOTOP, clipChildren=True)
    294         self.buttonCont.isTabStop = 1
    295         self.bottomContUnderlay = WindowSegmentUnderlay(bgParent=self.bottomCont)
    296 
    297     def ReconstructItemIcons(self):
    298         self.lastIcon = None
    299         self.iconCont.Flush()
    300         self.iconSprite = eveIcon.Icon(name='iconSprite', align=uiconst.TOPLEFT, parent=self.iconCont, state=uiconst.UI_HIDDEN)
    301         self.iconSprite.OnClick = (self.ShowInfo, self.iconSprite)
    302         self.iconSprite.GetMenu = (self.GetIconMenu, self.iconSprite)
    303         self.iconSprite.GetDragData = self.GetShipDragData
    304         self.charIcon = CharIcon(name='charIconSprite', align=uiconst.TOPLEFT, parent=self.iconCont, state=uiconst.UI_HIDDEN)
    305         self._UpdateIconContVisibility()
    306 
    307     def _on_compact_mode_changed(self, window):
    308         self.header.show_caption = not self.compact
    309         self._update_extend_content_into_header()
    310         self._UpdateMinSize()
    311 
    312     def _on_window_header_inset_changed(self, window):
    313         self._update_padding_of_content()
    314 
    315     def _on_content_padding_changed(self, window):
    316         self._update_padding_of_content()
    317 
    318     def _update_extend_content_into_header(self):
    319         self.extend_content_into_header = self.compact
    320 
    321     def _update_padding_of_content(self):
    322         pad_left, pad_top, pad_right, pad_bottom = self.content_padding
    323         _, inset_right = self.header_inset
    324         self.topCont.padding = (pad_left,
    325          pad_top,
    326          inset_right if self.compact else pad_right,
    327          0)
    328         self.buttonCont.padding = (pad_left,
    329          pad_bottom,
    330          pad_right,
    331          pad_bottom)
    332 
    333     def Prepare_Header_(self):
    334         self._SetHeader(SmallWindowHeader(show_caption=not self.compact))
    335 
    336     def OnActiveTrackingChange(self, isActivelyTracking):
    337         self.AdjustUIToActiveTrackingStatus(isActivelyTracking)
    338 
    339     def AdjustUIToActiveTrackingStatus(self, isTracking):
    340         caption = localization.GetByLabel('UI/Inflight/ActiveItem/SelectedItem')
    341         if isTracking:
    342             caption += ' - ' + localization.GetByLabel('UI/Inflight/ActiveItem/Tracking')
    343         self.SetCaption(caption)
    344 
    345     def _OnClose(self, *args):
    346         self.updateAutoTimer = None
    347 
    348     def ProcessSessionChange(self, isRemote, session, change):
    349         self.lastActionSerial = None
    350         self.lastActionDist = None
    351         self.lastCloakingStatus = None
    352         self.lastSessionChange = blue.os.GetWallclockTime()
    353 
    354     def OnItemIDSelected(self, itemID):
    355         self.itemID = itemID
    356         self.lastActionSerial = None
    357         self.lastActionDist = None
    358         self.lastBountyCheck = None
    359         self.bountyText = None
    360         uthread2.start_tasklet(self.UpdateAll, True)
    361 
    362     def OnStateChange(self, itemID, flag, true, *args):
    363         if itemID != session.shipid and not self.destroyed:
    364             uthread.new(self._OnStateChange, itemID, flag, true)
    365 
    366     @telemetry.ZONE_METHOD
    367     def _OnStateChange(self, itemID, flag, true):
    368         if flag == state.selected and true:
    369             self.OnItemIDSelected(itemID)
    370         if itemID == self.itemID and flag not in (state.selected, state.mouseOver):
    371             self.lastActionSerial = None
    372             self.lastActionDist = None
    373             self.UpdateAll()
    374 
    375     def OnDistSettingsChange(self):
    376         uthread.new(self._OnDistSettingsChange)
    377 
    378     @telemetry.ZONE_METHOD
    379     def _OnDistSettingsChange(self):
    380         self.ResetLastActionAndCloakingAndUpdateAll()
    381 
    382     def ResetLastActionAndCloakingAndUpdateAll(self):
    383         self.lastActionSerial = None
    384         self.lastCloakingStatus = None
    385         self.UpdateAll(True)
    386 
    387     def TryGetInvItem(self, itemID):
    388         if session.shipid is None:
    389             return
    390         ship = sm.GetService('invCache').GetInventoryFromId(session.shipid)
    391         if ship:
    392             for invItem in ship.List():
    393                 if invItem.itemID == itemID:
    394                     return invItem
    395 
    396     def OnExpanded(self, *args):
    397         if not self.inited:
    398             return
    399         super(SelectedItemWnd, self).OnExpanded(*args)
    400         self.ResetLastActionAndCloakingAndUpdateAll()
    401 
    402     def OnEndMaximize_(self, *args):
    403         if not self.inited:
    404             return
    405         self.ResetLastActionAndCloakingAndUpdateAll()
    406 
    407     def FadeInSelf(self):
    408         self.opacity = 0.001
    409         super(SelectedItemWnd, self).FadeInSelf()
    410         if not self.inited:
    411             return
    412         self.ResetLastActionAndCloakingAndUpdateAll()
    413 
    414     def SetNoContentHint(self, hint = None):
    415         if self.noContentHint is None and hint:
    416             self.noContentHint = eveLabel.EveCaptionMedium(text=hint, parent=self.sr.main, align=uiconst.RELATIVE, left=16, top=18, width=256, color=TextColor.SECONDARY)
    417         elif self.noContentHint:
    418             if hint:
    419                 self.noContentHint.text = hint
    420                 self.noContentHint.state = uiconst.UI_DISABLED
    421             else:
    422                 self.noContentHint.state = uiconst.UI_HIDDEN
    423 
    424     def FlushContent(self):
    425         self.nameLabel.text = ''
    426         self._clear_achievement_info()
    427         self.distanceLabel.display = False
    428         self.securityLabel.display = False
    429         self.timerLabel.display = False
    430         self.bountyLabel.display = False
    431         self.SetNoContentHint(localization.GetByLabel('UI/Inflight/ActiveItem/NoObjectSelected'))
    432         self.buttonCont.Flush()
    433         self.iconSprite.state = uiconst.UI_HIDDEN
    434         self.charIcon.state = uiconst.UI_HIDDEN
    435 
    436     @telemetry.ZONE_METHOD
    437     def UpdateAll(self, reconstructButtons = False):
    438         if self.destroyed:
    439             return
    440         if self.itemID == session.shipid:
    441             self.itemID = None
    442         bp = sm.GetService('michelle').GetBallpark()
    443         if self.IsWindowVisible() and bp and self.itemID:
    444             slimItem = uix.GetBallparkRecord(self.itemID)
    445             if slimItem:
    446                 ball = bp.GetBall(self.itemID)
    447                 if ball:
    448                     self._UpdateSlimItem(slimItem, reconstructButtons)
    449                     return
    450             invItem = self.TryGetInvItem(self.itemID)
    451             if invItem:
    452                 self._UpdateInvItem(invItem, reconstructButtons)
    453                 return
    454         self.lastActionSerial = None
    455         self.lastActionDist = None
    456         self.FlushContent()
    457 
    458     def _UpdateSlimItem(self, slimItem, reconstructButtons):
    459         self.SetNoContentHint(None)
    460         name_text = uix.GetSlimItemName(slimItem)
    461         jumpExtraText = self._GetJumpExtraText(slimItem)
    462         if jumpExtraText:
    463             name_text += jumpExtraText
    464         self.nameLabel.text = name_text
    465         self.nameRowCont.char_id = slimItem.charID
    466         self.nameRowCont.char_name = name_text
    467         self._update_achievement_info(slimItem)
    468         distanceText = self._GetDistanceText()
    469         self.distanceLabel.text = distanceText or u''
    470         self.distanceLabel.display = bool(distanceText)
    471         securityText = self._GetSecurityStatusTex(slimItem)
    472         self.securityLabel.text = securityText or u''
    473         self.securityLabel.display = bool(securityText)
    474         timerText = GetTimerText(slimItem)
    475         if timerText.startswith('<br>'):
    476             timerText = timerText[4:]
    477         self.timerLabel.text = timerText
    478         self.timerLabel.display = bool(timerText)
    479         bountyText = self.GetBountyText(slimItem)
    480         self.bountyLabel.text = bountyText or u''
    481         self.bountyLabel.display = bool(bountyText)
    482         self._CheckUpdateIcons(slimItem, slimItem.typeID)
    483         self.UpdateKillRightsIconVisibility(slimItem)
    484         if reconstructButtons or self._CheckDistanceChanged() or self._RequiresRegularUpdates(slimItem):
    485             actions = GetMenuService().UnmergedCelestialMenu(slimItem)
    486             self.ReconstructButtons(slimItem, None, actions)
    487         if not self.updateAutoTimer:
    488             self.updateAutoTimer = AutoTimer(500, self.UpdateAll)
    489 
    490     def _update_achievement_info(self, slim_item):
    491         char_id = slim_item.charID
    492         if char_id is None:
    493             self._clear_achievement_info()
    494             return
    495         category_id = slim_item.categoryID
    496         if category_id and category_id != const.categoryShip:
    497             self._clear_achievement_info()
    498             return
    499         if self.character_title_chip is not None:
    500             if self.character_title_chip.character_id == char_id:
    501                 return
    502             self._clear_achievement_info()
    503         from achievement_system.client.ui.chip.character_title import CharacterTitleChip
    504         self.character_title_chip = CharacterTitleChip(name='character_title_chip', parent=self.titleCont, align=uiconst.TOLEFT, character_id=char_id)
    505 
    506     def _clear_achievement_info(self):
    507         self.titleCont.Flush()
    508         self.character_title_chip = None
    509 
    510     def _GetSlimItemText(self, slimItem, density):
    511         text = uix.GetSlimItemName(slimItem)
    512         jumpExtraText = self._GetJumpExtraText(slimItem)
    513         if jumpExtraText:
    514             text += jumpExtraText
    515         distanceText = self._GetDistanceText()
    516         securityText = self._GetSecurityStatusTex(slimItem)
    517         if density == HEIGHTSTATE_LARGE:
    518             if distanceText:
    519                 text += '<br>%s' % distanceText
    520             if securityText:
    521                 text += '<br>%s' % securityText
    522         else:
    523             if distanceText:
    524                 text = '%s - %s' % (text, distanceText)
    525             if securityText:
    526                 text += ' (%s)' % securityText
    527         timerText = GetTimerText(slimItem)
    528         if timerText:
    529             text += timerText
    530         bountyText = self.GetBountyText(slimItem)
    531         if bountyText:
    532             if density == HEIGHTSTATE_SMALL:
    533                 text += ' (%s)' % bountyText
    534             else:
    535                 text += '<br>' + bountyText
    536         return text
    537 
    538     def _UpdateInvItem(self, invItem, reconstructButtons):
    539         self._CheckUpdateIcons(None, invItem.typeID)
    540         self.UpdateKillRightsIconVisibility()
    541         self.nameLabel.text = uix.GetItemName(invItem)
    542         self.distanceLabel.display = False
    543         self.securityLabel.display = False
    544         self.timerLabel.display = False
    545         bountyText = self.GetBountyText()
    546         self.bountyLabel.text = bountyText or u''
    547         self.bountyLabel.display = bool(bountyText)
    548         self.SetNoContentHint(None)
    549         self.UpdateKillRightsIconVisibility()
    550         if reconstructButtons:
    551             data = [(invItem, 0, None)]
    552             actions = StartMenuService().InvItemMenu(data)
    553             self.ReconstructButtons(None, invItem, actions)
    554 
    555     def _CheckDistanceChanged(self):
    556         bp = sm.GetService('michelle').GetBallpark()
    557         ball = bp.GetBall(self.itemID)
    558         distanceToItem = getattr(ball, 'surfaceDist', None)
    559         reconstructButtons = False
    560         if distanceToItem is not None:
    561             myBallMode = None
    562             myball = bp.GetBall(session.shipid)
    563             if myball:
    564                 myBallMode = myball.mode
    565                 if self.lastCloakingStatus is None or self.lastCloakingStatus != myball.isCloaked:
    566                     reconstructButtons = True
    567                 self.lastCloakingStatus = myball.isCloaked
    568             if not self.lastActionDist or myBallMode != self.lastActionDist[1] or self.CheckDistanceUpdate(self.lastActionDist[0], distanceToItem):
    569                 self.lastActionDist = (distanceToItem, myBallMode)
    570                 reconstructButtons = True
    571         return reconstructButtons
    572 
    573     def _GetDistanceText(self):
    574         bp = sm.GetService('michelle').GetBallpark()
    575         ball = bp.GetBall(self.itemID)
    576         distanceToItem = getattr(ball, 'surfaceDist', None)
    577         if distanceToItem is not None:
    578             return FmtDist(distanceToItem, maxdemicals=1)
    579 
    580     def _GetSecurityStatusTex(self, slimItem):
    581         securityStatusText = ''
    582         sec = slimItem.securityStatus
    583         if sec:
    584             securityStatusText = localization.GetByLabel('UI/Inflight/ActiveItem/SelectedItemSecurity', secStatus=sec)
    585         return securityStatusText
    586 
    587     def _GetJumpExtraText(self, slimItem):
    588         if slimItem.groupID == const.groupStargate:
    589             return self._GetStargateSecurityText(slimItem)
    590         if HasShipcasterComponent(slimItem.typeID) or HasStarterShipcasterComponent(slimItem.typeID):
    591             shipcasterText = GetShipcasterText(slimItem)
    592             if shipcasterText:
    593                 return '<br>%s' % shipcasterText
    594             return ''
    595         return ''
    596 
    597     def _GetStargateSecurityText(self, slimItem):
    598         return u' ({security_status})'.format(security_status=eveformat.solar_system_security_status(slimItem.jumps[0].locationID))
    599 
    600     def GetBountyText(self, slimItem = None):
    601         bountyItemID = None
    602         bountyTypeID = None
    603         bountySlim = None
    604         if slimItem:
    605             if slimItem.categoryID == const.categoryEntity:
    606                 bountyTypeID = slimItem.typeID
    607                 bountySlim = slimItem
    608             elif slimItem.charID:
    609                 bountyItemID = slimItem.charID
    610                 bountySlim = slimItem
    611         if (bountyItemID, bountyTypeID) != self.lastBountyCheck:
    612             bounty = self._GetBountyAmount(bountyTypeID, bountySlim)
    613             if bounty:
    614                 self.bountyText = localization.GetByLabel('UI/Common/BountyAmount', bountyAmount=eveFormat.FmtISK(bounty, 0))
    615             else:
    616                 self.bountyText = None
    617             self.lastBountyCheck = (bountyItemID, bountyTypeID)
    618         bountyText = ''
    619         if self.bountyText:
    620             bountyText += self.bountyText
    621         return bountyText
    622 
    623     def UpdateKillRightsIconVisibility(self, slimItem = None):
    624         displayUtilMenu = False
    625         if slimItem:
    626             killRightID, price = self.bountySvc.GetBestKillRight(slimItem.charID)
    627             self.killRightsUtilMenu.UpdateKillRightInfo(killRightID, price, slimItem.charID, slimItem.itemID)
    628             stateSvc = sm.GetService('stateSvc')
    629             if killRightID is not None and not (stateSvc.CheckSuspect(slimItem) or stateSvc.CheckCriminal(slimItem)) and slimItem.categoryID != const.categoryStructure:
    630                 displayUtilMenu = True
    631         self.killRightsUtilMenuCont.display = displayUtilMenu
    632 
    633     def _CheckUpdateIcons(self, slimItem, typeID):
    634         charID = None
    635         categoryID = None
    636         if slimItem:
    637             if slimItem.categoryID == const.categoryShip:
    638                 if idCheckers.IsCharacter(slimItem.charID):
    639                     charID = slimItem.charID
    640                     categoryID = slimItem.categoryID
    641         if self.lastIcon != (typeID, self.itemID, charID):
    642             uthread2.start_tasklet(self.UpdateIcons, typeID, charID)
    643             self.lastIcon = (typeID, self.itemID, charID)
    644         else:
    645             self.iconSprite.state = uiconst.UI_NORMAL
    646             if categoryID == const.categoryShip and charID:
    647                 self.charIcon.state = uiconst.UI_NORMAL
    648 
    649     def _RequiresRegularUpdates(self, slimItem):
    650         if HasStructureTimer(slimItem) or HasSkyhookTimer(slimItem):
    651             return True
    652         if HasShipcasterComponent(slimItem.typeID) or HasStarterShipcasterComponent(slimItem.typeID):
    653             return True
    654         if HasTowGameObjectiveComponent(slimItem.typeID):
    655             return True
    656         if HasFilamentSpoolupComponent(slimItem.typeID):
    657             return True
    658         return False
    659 
    660     @telemetry.ZONE_METHOD
    661     def DoBallsRemove(self, pythonBalls, isRelease):
    662         for ball, slimItem, terminal in pythonBalls:
    663             self.DoBallRemove(ball, slimItem, terminal)
    664 
    665     def DoBallRemove(self, ball, slimItem, terminal):
    666         if self.IsWindowVisible() and ball and ball.id == self.itemID:
    667             uthread.pool('ActiveItem::UpdateAll', self.UpdateAll, True)
    668 
    669     def UpdateIcons(self, typeID, charID):
    670         self.iconSprite.LoadIconByTypeID(typeID, itemID=self.itemID, ignoreSize=True)
    671         self.iconSprite.typeID = typeID
    672         self.iconSprite.itemID = self.itemID
    673         categoryID = evetypes.GetCategoryID(typeID)
    674         self.iconSprite.categoryID = categoryID
    675         self.iconSprite.state = uiconst.UI_NORMAL
    676         if categoryID == const.categoryShip and charID:
    677             typeID = cfg.eveowners.Get(charID).typeID
    678             self.charIcon.SetID(typeID, charID)
    679             self.charIcon.state = uiconst.UI_NORMAL
    680         else:
    681             self.charIcon.state = uiconst.UI_HIDDEN
    682 
    683     def GetShipDragData(self):
    684         categoryID = self.iconSprite.categoryID
    685         if categoryID != const.categoryShip:
    686             return []
    687         return ShipDragData(self.iconSprite.typeID, self.iconSprite.itemID, self.charIcon.itemID)
    688 
    689     def _GetBountyAmount(self, bountyTypeID, slimItem):
    690         bounty = None
    691         if bountyTypeID:
    692             bounty = get_entity_bounty(bountyTypeID, slimItem.itemID)
    693         elif slimItem:
    694             if self.bountySvc.CanHaveBounty(slimItem):
    695                 bounty = self.bountySvc.GetBounty(slimItem.charID, slimItem.corpID, slimItem.allianceID)
    696         return bounty
    697 
    698     def GetNpcBounty(self, bountyAmount):
    699         return (bountyAmount, None, None)
    700 
    701     def ShowInfo(self, btn, *args):
    702         if btn and btn.typeID:
    703             sm.GetService('info').ShowInfo(btn.typeID, btn.itemID)
    704 
    705     def GetIconMenu(self, btn, *args):
    706         if btn and btn.typeID:
    707             return GetMenuService().GetMenuFromItemIDTypeID(getattr(btn, 'itemID', None), btn.typeID)
    708 
    709     def CheckDistanceUpdate(self, lastdist, dist):
    710         diff = abs(lastdist - dist)
    711         if not diff:
    712             return False
    713         elif dist:
    714             return bool(diff / dist > 0.01)
    715         else:
    716             return bool(lastdist != dist)
    717 
    718     def IsWindowVisible(self):
    719         return bool(not self.IsCollapsed() and not self.IsMinimized() and self.opacity > 0 and self.display)
    720 
    721     @telemetry.ZONE_METHOD
    722     def ReconstructButtons(self, slimItem, invItem, actions):
    723         if self.destroyed:
    724             return
    725         if not self.IsWindowVisible():
    726             self.updateAutoTimer = None
    727             return
    728         menuData = menuDataFactory.CreateMenuDataFromRawTuples(actions)
    729         if menuData is None:
    730             menuData = MenuData()
    731         actionNamesSerial, validActionsByName = self._GetValidActionsByName(menuData)
    732         if actionNamesSerial == self.lastActionSerial:
    733             return
    734         self.buttonCont.Flush()
    735         visibleActionNames = self._GetVisibleActionNamesOrdered(invItem, slimItem, validActionsByName)
    736         reasonsWhyNotAvailable = getattr(actions, 'reasonsWhyNotAvailable', {})
    737         for actionName in visibleActionNames:
    738             if actionName is None:
    739                 continue
    740             menuEntryData = self._GetMenuEntryData(actionName, validActionsByName)
    741             disabledHint = None
    742             if not menuEntryData:
    743                 menuEntryData = MenuEntryData(text=self._GetDisabledMenuLabel(actionName))
    744                 disabledHint = reasonsWhyNotAvailable.get(actionName, localization.GetByLabel('UI/Menusvc/MenuHints/NoReasonGiven'))
    745             self._ConstructButton(menuEntryData, disabledHint)
    746 
    747         self._UpdateButtonSize()
    748         self.lastActionSerial = actionNamesSerial
    749 
    750     def _GetDisabledMenuLabel(self, actionName):
    751         if actionName in ('UI/Inflight/WarpToWithinDistance', 'UI/Inflight/WarpToMoonMiningPoint'):
    752             return MenuLabel(actionName, {'distance': 0})
    753         elif actionName == 'UI/Inflight/JumpByShipcaster':
    754             return MenuLabel(actionName, {'text': localization.GetByLabel('UI/Inflight/Jump')})
    755         else:
    756             return MenuLabel(actionName)
    757 
    758     def _GetMenuEntryData(self, actionName, validActionsByName):
    759         menuEntryData = validActionsByName.get(actionName, None)
    760         if not menuEntryData and actionName in TOGGLE_ACTIONS:
    761             menuEntryData = validActionsByName.get(TOGGLE_ACTIONS[actionName], None)
    762         return menuEntryData
    763 
    764     def _GetNotAvailableHint(self, actionName, reasonsWhyNotAvailable, validActionsByName):
    765         if actionName not in validActionsByName:
    766             return reasonsWhyNotAvailable.get(actionName, localization.GetByLabel('UI/Menusvc/MenuHints/NoReasonGiven'))
    767 
    768     def _GetVisibleActionNamesOrdered(self, invItem = None, slimItem = None, validActionsByName = None):
    769         if slimItem:
    770             typeID = slimItem.typeID
    771             groupID = slimItem.groupID
    772             categoryID = slimItem.categoryID
    773         elif invItem:
    774             typeID = invItem.typeID
    775             groupID = invItem.groupID
    776             categoryID = invItem.categoryID
    777         else:
    778             typeID = None
    779             groupID = None
    780             categoryID = None
    781         actions = []
    782         if 'UI/Inflight/AlignTo' not in validActionsByName:
    783             approachLabelPath = 'UI/Inflight/ApproachObject'
    784         else:
    785             approachLabelPath = 'UI/Inflight/AlignTo'
    786         actions += [approachLabelPath, 'UI/Inflight/Submenus/WarpToWithin']
    787         if typeID and typeID in ACTIONS_BY_TYPEID:
    788             actions += ACTIONS_BY_TYPEID[typeID]
    789         elif groupID and groupID in ACTIONS_BY_GROUPID:
    790             actions += ACTIONS_BY_GROUPID[groupID]
    791         elif categoryID and categoryID in ACTIONS_BY_CATEGORYID:
    792             actions += ACTIONS_BY_CATEGORYID[categoryID]
    793         item = slimItem or invItem
    794         if item:
    795             if HasCargoBayComponent(typeID) and not HasItemTrader(typeID):
    796                 if HasUnderConstructionComponent(typeID):
    797                     actions.append('UI/Inflight/SpaceComponents/UnderConstruction/Access')
    798                 elif cargobay.IsAccessibleByCharacter(item, session.charid):
    799                     actions.append('UI/Commands/OpenCargo')
    800             if HasItemTrader(typeID):
    801                 actions.append('UI/Inflight/SpaceComponents/ItemTrader/Access')
    802         actions = itertoolsext.remove_duplicates_and_preserve_order(actions)
    803         actions += ACTIONS_ALWAYS_VISIBLE
    804         return actions
    805 
    806     def _GetValidActionsByName(self, menuData):
    807         menuEntries = self.GetAllActionsFromMenus(menuData)
    808         actionNamesSerial = ''
    809         validActionsByName = {}
    810         for menuEntryData in menuEntries:
    811             labelPath = menuEntryData.GetLabelPath()
    812             if labelPath in selectedItemConst.ICON_ID_AND_CMD_BY_ACTIONID:
    813                 validActionsByName[labelPath] = menuEntryData
    814                 if labelPath:
    815                     actionNamesSerial += '%s_' % labelPath
    816 
    817         return (actionNamesSerial, validActionsByName)
    818 
    819     def _ConstructButton(self, menuEntryData, disabledHint):
    820         actionObject = selectedItemConst.ICON_ID_AND_CMD_BY_ACTIONID[menuEntryData.GetLabelPath()]
    821         SelectedItemButton(name=actionObject.uniqueUiName or menuEntryData.GetLabelPath(), parent=self.buttonCont, align=uiconst.TOLEFT, uniqueUiName=actionObject.uniqueUiName, menuEntryData=menuEntryData, func=lambda *args: self.on_selected_item_button(menuEntryData.func), cmdName=CMD_BY_ACTIONID.get(menuEntryData.GetLabelPath(), ''), disabledHint=disabledHint, enabled=not bool(disabledHint), padRight=BUTTON_PADDING)
    822 
    823     def on_selected_item_button(self, func):
    824         GetAudioService().SendUIEvent('ui_radial_select_play')
    825         func()
    826 
    827     def GetAllActionsFromMenus(self, menuData):
    828         allActionsDict = OrderedDict()
    829         subMenuActions = []
    830         for menuEntryData in menuData.GetEntries():
    831             if not menuEntryData or not menuEntryData.IsEnabled():
    832                 continue
    833             allActionsDict[menuEntryData.GetLabelPath()] = menuEntryData
    834             if menuEntryData.GetLabelPath() in SUB_MENU_ACTIONS:
    835                 for eachSubOption in menuEntryData.GetSubMenuData() or []:
    836                     if eachSubOption is not None:
    837                         subMenuActions.append(eachSubOption)
    838 
    839         for menuEntryData in subMenuActions:
    840             if menuEntryData.GetLabelPath() not in allActionsDict:
    841                 allActionsDict[menuEntryData.GetLabelPath()] = menuEntryData
    842 
    843         return allActionsDict.values()
    844 
    845     def _IsDisabledEntry(self, each):
    846         if isinstance(each, MenuEntryData):
    847             return not each.IsEnabled()
    848         else:
    849             return each[1] == DISABLED_ENTRY0
    850 
    851     def OnPlanetViewChanged(self, newPlanetID, oldPlanetID):
    852         for planetID in (newPlanetID, oldPlanetID):
    853             if planetID == self.itemID:
    854                 self.OnItemIDSelected(self.itemID)
    855 
    856     def GetMenuMoreOptions(self):
    857         menu = super(SelectedItemWnd, self).GetMenuMoreOptions()
    858         menu += self.GetMenu()
    859         return menu
    860 
    861     def GetMenu(self, *args):
    862         menu = super(SelectedItemWnd, self).GetMenu(*args)
    863         menu.AddEntry(MenuLabel('UI/Generic/Copy'), self._CopyText)
    864         if self.charIcon.display:
    865             menu.AddSeparator()
    866             menu.AddEntry(MenuLabel('UI/Common/Pilot', {'character': self.charIcon.itemID}), subMenuData=lambda : self.charIcon.GetMenu(), texturePath=eveicon.person)
    867         return menu
    868 
    869     def _CopyText(self):
    870         text = self._GetTextCopyContent()
    871         text = eveformat.strip_tags(text, tags=['localized'])
    872         if text is not None:
    873             blue.pyos.SetClipboardData(text)
    874 
    875     def _GetTextCopyContent(self):
    876         ballpark = sm.GetService('michelle').GetBallpark()
    877         if ballpark is None or self.itemID is None:
    878             return
    879         slim_item = uix.GetBallparkRecord(self.itemID)
    880         if slim_item is None:
    881             return
    882         ball = ballpark.GetBall(self.itemID)
    883         if ball:
    884             text = self._GetSlimItemText(slim_item, density=HEIGHTSTATE_LARGE)
    885             return text.replace(u'<br>', '\n')
    886         item = self.TryGetInvItem(self.itemID)
    887         if item:
    888             return u'{name}\n{bounty}'.format(name=uix.GetItemName(item), bounty=self.GetBountyText())
    889 
    890 
    891 class NameRowContainer(Container):
    892 
    893     def __init__(self, *args, **kwargs):
    894         self.char_id = None
    895         self.char_name = None
    896         super(NameRowContainer, self).__init__(*args, **kwargs)
    897 
    898     def LoadTooltipPanel(self, panel, *args):
    899         if self.char_id is None or self.char_name is None:
    900             return
    901         panel.LoadStandardSpacing()
    902         panel.columns = 1
    903         from character.client.identifier import CharacterIdentifier
    904         from cosmetics.client.character.title.api.repository import get_repository
    905         equipped_state = get_repository().get_equipped_title(CharacterIdentifier(self.char_id))
    906         has_title = equipped_state is not None and equipped_state.equipped_title is not None
    907         if has_title:
    908             from achievement_system.client.ui.chip.character_title import CharacterTitleChip
    909             title_chip = CharacterTitleChip(name='character_title_chip', character_id=self.char_id)
    910             panel.AddTextBodyLabel(text=self.char_name)
    911             panel.AddCell(title_chip)
    912         else:
    913             panel.AddTextBodyLabel(text=self.char_name)