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

panelFitting.py (33751B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\shared\info\shipInfoPanels\panelFitting.py
      2 from collections import defaultdict
      3 import collections
      4 import itertools
      5 import math
      6 import trinity
      7 import eveformat
      8 import eveicon
      9 import evetypes
     10 import localization
     11 import dogma.data
     12 import uthread2
     13 import dogma.data as dogma_data
     14 from carbonui.control.button import Button
     15 from carbonui.control.buttonIcon import ButtonIcon
     16 from carbonui.primitives.transform import Transform
     17 from carbonui.util.color import Color
     18 from dogma import const as dogmaConst
     19 from carbon.common.script.util.format import FmtAmt
     20 from carbonui.control.scrollContainer import ScrollContainer
     21 from dogma.attributes.format import GetFormattedAttributeAndValue, GetFormatAndValue
     22 from eve.client.script.ui import eveColor
     23 from eve.client.script.ui.services.menuSvcExtras.openFunctions import SimulateFitting
     24 from eve.client.script.ui.shared.fittingMgmtWindow import ViewFitting
     25 from eve.client.script.ui.shared.fittingScreen.fittingUtil import GetColoredText
     26 from eve.client.script.ui.shared.fittingScreen.tryFit import GetParentShipInfo
     27 from eve.client.script.ui.shared.info.shipInfoCollapsibleGroup import CollapsibleGroup
     28 from eve.client.script.ui.shared.info.shipInfoPanels.panelConst import TAB_FITTING
     29 from eve.client.script.ui.shared.traits import TraitsContainer
     30 from eve.common.lib import appConst as const
     31 from carbonui import Align, TextColor, uiconst, Density, AxisAlignment
     32 from carbonui.text.styles import TextBody
     33 from carbonui.primitives.container import Container
     34 from carbonui.primitives.containerAutoSize import ContainerAutoSize
     35 from carbonui.primitives.sprite import Sprite
     36 from carbonui.primitives.flowcontainer import FlowContainer
     37 from eve.client.script.ui.shared.info.shipInfoConst import ANGLE_FITTING, CONTENT_PADDING, EXPANDED
     38 from eve.client.script.ui.shared.info.shipInfoListEntries import _ListEntryBase, ListEntry, ListEntryStatusBarAttribute, ListEntryAttribute, ListEntryEveType
     39 from eve.client.script.ui.shared.info.shipInfoPanels.panelBase import PanelBase
     40 from eve.client.script.ui.shared.fitting.fittingControllerUtil import GetFittingController
     41 from eve.common.script.sys import eveCfg, idCheckers
     42 from eve.common.script.util.eveFormat import FmtISK
     43 from inventorycommon.typeHelpers import GetAveragePrice
     44 from inventorycommon.util import IsSubsystemFlagVisible, IsModularShip
     45 from localization import GetByLabel
     46 from utillib import KeyVal
     47 from fsdBuiltData.common.iconIDs import GetIconFile
     48 BAD_COLOR = (70 / 256.0, 26 / 256.0, 13.0 / 256.0)
     49 GOOD_COLOR = (240 / 256.0, 90 / 256.0, 50.0 / 256.0)
     50 EMPTY_COLOR = (0.25,
     51  0.25,
     52  0.25,
     53  0.75)
     54 FITTING_SLOT_INFO = [{'label': 'UI/InfoWindow/FittingHighPowerSlot',
     55   'attributeID': const.attributeHiSlots,
     56   'flags': const.hiSlotFlags,
     57   'effectID': const.effectHiPower},
     58  {'label': 'UI/InfoWindow/FittingMediumPowerSlots',
     59   'attributeID': const.attributeMedSlots,
     60   'flags': const.medSlotFlags,
     61   'effectID': const.effectMedPower},
     62  {'label': 'UI/InfoWindow/FittingLowPowerSlots',
     63   'attributeID': const.attributeLowSlots,
     64   'flags': const.loSlotFlags,
     65   'effectID': const.effectLoPower},
     66  {'label': 'UI/InfoWindow/FittingRigSlots',
     67   'attributeID': const.attributeUpgradeSlotsLeft,
     68   'flags': const.rigSlotFlags,
     69   'effectID': const.effectRigSlot},
     70  {'label': 'UI/InfoWindow/FittingServiceSlots',
     71   'attributeID': const.attributeServiceSlots,
     72   'flags': const.serviceSlotFlags,
     73   'effectID': const.effectServiceSlot},
     74  {'label': 'UI/InfoWindow/FittingSubsystemSlots',
     75   'attributeID': const.attributeSubSystemSlot,
     76   'flags': const.subsystemSlotFlags,
     77   'effectID': const.effectSubSystem}]
     78 
     79 class PanelFitting(PanelBase):
     80 
     81     def ApplyAttributes(self, attributes):
     82         self.capCapacityAttribute = dogma_data.get_attribute(dogmaConst.attributeCapacitorCapacity)
     83         self.rechargeRateAttribute = dogma_data.get_attribute(dogmaConst.attributeRechargeRate)
     84         self.maxcap = None
     85         self.fitting_controller = None
     86         super(PanelFitting, self).ApplyAttributes(attributes)
     87         dogmaLocation = sm.GetService('clientDogmaIM').GetDogmaLocation()
     88         self.item = None
     89         if dogmaLocation.IsItemLoaded(self.itemID):
     90             self.item = dogmaLocation.GetItem(self.itemID)
     91 
     92     def _construct_content(self):
     93         uthread2.Yield()
     94         self._construct_ship_overlay_section()
     95         self._construct_main_section()
     96         self._construct_sub_systems_for_type()
     97 
     98     def _gather_data(self):
     99         self.turretSlotsUsed = 0
    100         self.launcherSlotsUsed = 0
    101         self.entryList = []
    102         inventory = self._controller.get_ship_contents()
    103         for slotKey, slotInfo in enumerate(FITTING_SLOT_INFO):
    104             modulesByType = collections.defaultdict(list)
    105             chargesByFlag = collections.defaultdict(list)
    106             for item in filter(lambda item: item.flagID in slotInfo['flags'], inventory):
    107                 if IsCharge(item):
    108                     chargesByFlag[item.flagID].append(item)
    109                 else:
    110                     modulesByType[item.typeID].append(item)
    111 
    112             usedSlotCount = 0
    113             itemEntries = []
    114             for typeID, items in modulesByType.iteritems():
    115                 if HasEffect(typeID, slotInfo['effectID']):
    116                     usedSlotCount += len(items)
    117                 if HasEffect(typeID, const.effectTurretFitted):
    118                     self.turretSlotsUsed += len(items)
    119                 if HasEffect(typeID, const.effectLauncherFitted):
    120                     self.launcherSlotsUsed += len(items)
    121                 lowestFlagID = min((item.flagID for item in items))
    122                 sortKey = (slotKey, lowestFlagID)
    123                 entry = self.GetFittingEntry(typeID, len(items))
    124                 itemEntries.append(entry)
    125                 chargeQuantityByType = collections.defaultdict(int)
    126                 for charge in itertools.chain.from_iterable((chargesByFlag[item.flagID] for item in items)):
    127                     chargeQuantityByType[charge.typeID] += max(charge.quantity, 1)
    128 
    129                 for chargeType, chargeQuantity in chargeQuantityByType.iteritems():
    130                     sortKey = (slotKey, lowestFlagID, chargeType)
    131                     entry = self.GetFittingEntry(chargeType, chargeQuantity, isCharge=True)
    132                     itemEntries.append(entry)
    133 
    134             entry = self.GetFittingSlotEntry(slotInfo, usedSlotCount)
    135             self.entryList.append(entry)
    136             self.entryList.extend(itemEntries)
    137 
    138         self.entryList.extend(self.GetDroneEntries(inventory))
    139         if self.itemID != session.shipid:
    140             return
    141         dogma_location = sm.GetService('clientDogmaIM').GetDogmaLocation()
    142         if not dogma_location.GetShipItem():
    143             return
    144         self.fitting_controller = GetFittingController(self.itemID)
    145 
    146     def _construct_main_section(self):
    147         self.scrollContainer = ScrollContainer(name='fittingScroll', parent=self.rightCont, align=Align.TOALL)
    148         self.attributeCont = ContainerAutoSize(name='fittingCont', parent=self.scrollContainer, align=Align.TOTOP)
    149         self.headerCont = Container(name='simulateCont', parent=self.attributeCont, align=Align.TOTOP, height=32, bgColor=(1, 1, 1, 0.1), padBottom=4, state=uiconst.UI_NORMAL)
    150         linkCont = Container(name='linkCont', parent=self.headerCont, align=Align.TOLEFT, width=16, padLeft=8)
    151         linkIcon = ButtonIcon(name='linkIcon', parent=linkCont, align=Align.CENTER, texturePath=eveicon.link, iconSize=16, state=uiconst.UI_NORMAL)
    152         linkIcon.OnClick = self.OnFittingIconClicked
    153         linkIcon.GetDragData = self.GetFittingDragData
    154         linkIcon.isDragObject = True
    155         Button(name='simulateButton', parent=self.headerCont, align=Align.CENTERRIGHT, density=Density.COMPACT, label=GetByLabel('UI/InfoWindow/SimulateButton'), padRight=4, func=self.OnSimulateClicked)
    156         textCont = Container(name='textCont', parent=self.headerCont, align=Align.TOALL)
    157         TextBody(name='label', parent=textCont, align=Align.CENTERLEFT, text=GetByLabel('UI/InfoWindow/FittingLabel'), padLeft=8)
    158         infoSvc = sm.GetService('info')
    159         attributeDictForType, attributeDict = infoSvc.GetAttributeDictForItem(self.itemID, self.typeID)
    160         modifiedAttributesDict = infoSvc.FindAttributesThatHaveBeenModified(attributeDictForType, attributeDict)
    161         for attributeID in [const.attributeCpuOutput, const.attributePowerOutput, const.attributeUpgradeCapacity]:
    162             modifiedAttribute = modifiedAttributesDict.get(attributeID, None)
    163             statusBarData = infoSvc.GetStatusBarDataForAttribute(attributeID, self.itemID, self.typeID, modifiedAttribute)
    164             if statusBarData:
    165                 texturePath = GetIconFile(statusBarData['iconID'])
    166                 ListEntryStatusBarAttribute(label=statusBarData['label'], text=statusBarData['text'], texturePath=texturePath, itemID=self.itemID, attributeID=statusBarData['attributeID'], modifiedAttribute=statusBarData['modifiedAttribute'], color=statusBarData['color'], value=statusBarData['value'], parent=self.attributeCont)
    167                 continue
    168             if attributeID not in attributeDictForType:
    169                 continue
    170             attributeData = infoSvc.GetDataForAttribute(attributeID, attributeDictForType[attributeID], self.itemID, self.typeID, modifiedAttribute)
    171             if attributeData:
    172                 texturePath = GetIconFile(attributeData['iconID'])
    173                 ListEntryAttribute(label=attributeData['label'], text=attributeData['text'], texturePath=texturePath, itemID=self.itemID, attributeID=attributeData['attributeID'], modifiedAttribute=attributeData['modifiedAttribute'], parent=self.attributeCont)
    174 
    175         self.GetHardpointsEntry(self.turretSlotsUsed, self.launcherSlotsUsed, self.attributeCont)
    176         for entry in self.entryList:
    177             if entry is None:
    178                 continue
    179             ConstructFittingEntry(entry, self.attributeCont)
    180 
    181     def _construct_sub_systems_for_type(self):
    182         if not IsModularShip(self.typeID):
    183             return
    184         sub_system_header = Container(name='sub_system_header', parent=self.attributeCont, align=Align.TOTOP, height=32, bgColor=(1, 1, 1, 0.1), padBottom=8, padTop=8, state=uiconst.UI_NORMAL)
    185         textCont = Container(name='textCont', parent=sub_system_header, align=Align.TOALL)
    186         TextBody(name='label', parent=textCont, align=Align.CENTERLEFT, text=GetByLabel('UI/InfoWindow/FittingAvailableSubsystems'), padLeft=8)
    187         sub_systems_by_group = self.get_sub_systems_by_group()
    188         for group_id in sub_systems_by_group:
    189             group = CollapsibleGroup(parent=self.attributeCont, align=Align.TOTOP, groupName=evetypes.GetGroupNameByGroup(group_id), groupIcon=GetIconFile(evetypes.GetGroupIconIDByGroup(group_id)), padBottom=8, bold=False, iconSize=24, iconColor=(1, 1, 1, 1))
    190             for sub_system_type_id in sub_systems_by_group[group_id]:
    191                 sub_system_entry = ListEntrySubsystem(parent=group.mainCont, text=evetypes.GetName(sub_system_type_id), typeID=sub_system_type_id, align=Align.TOTOP)
    192 
    193             group.SetCollapsed(True)
    194 
    195     def _construct_average_price(self):
    196         self.avgPriceCont = None
    197         avg_price = GetAveragePrice(self.typeID)
    198         if not avg_price:
    199             avg_price = 0
    200         fitting = self._controller.get_fitting_data()['fitData']
    201         for typeID, flag, qty in fitting:
    202             price = GetAveragePrice(typeID)
    203             if not price:
    204                 continue
    205             avg_price += price * qty
    206 
    207         if not avg_price:
    208             return
    209         self.avgPriceCont = ContainerAutoSize(name='avgPriceCont', parent=self.leftCont, align=Align.TOTOP, height=32, padTop=8)
    210         labelCont = ContainerAutoSize(name='labelCont', parent=self.avgPriceCont, align=Align.TOTOP)
    211         label = TextBody(name='label', parent=labelCont, align=Align.TOPLEFT, text=localization.GetByLabel('UI/InfoWindow/EstimatedPrice'), color=TextColor.SECONDARY)
    212         valueCont = ContainerAutoSize(name='valueCont', parent=self.avgPriceCont, align=Align.TOTOP)
    213         iconCont = Sprite(name='iconCont', parent=valueCont, align=Align.TOPLEFT, height=16, width=16, texturePath=eveicon.isk)
    214         self.avg_price = TextBody(name='avgPrice', parent=valueCont, align=Align.TOPLEFT, padLeft=20, text=FmtISK(avg_price))
    215 
    216     def _construct_ship_overlay_section(self):
    217         self._construct_average_price()
    218         Sprite(parent=self.leftCont, align=Align.CENTER_PRESERVE_ASPECT, aspectRatio=1, texturePath='res:/UI/Texture/classes/ShipInfo/circle_flair.png', state=uiconst.UI_DISABLED, blendMode=trinity.TR2_SBM_ADD, padLeft=-CONTENT_PADDING[EXPANDED])
    219         if self.fitting_controller is None:
    220             return
    221         bottom = FlowContainer(name='bottom', parent=self.leftCont, align=Align.TOBOTTOM, contentSpacing=(16, 8), crossAxisAlignment=AxisAlignment.END)
    222         left = ContainerAutoSize(name='left', parent=bottom, align=Align.NOALIGN)
    223         right = ContainerAutoSize(name='right', parent=bottom, align=Align.NOALIGN)
    224         offenceLabel = TextBody(parent=left, align=Align.TOPLEFT, text=localization.GetByLabel('UI/Fitting/FittingWindow/Offense'))
    225         offenceValue = TextBody(parent=left, align=Align.TOPLEFT, text=localization.GetByLabel('UI/Fitting/FittingWindow/DpsLabel', dps=self.fitting_controller.GetTotalDps().value), color=TextColor.SECONDARY, padTop=offenceLabel.height)
    226         defenseLabel = TextBody(parent=left, align=Align.TOPLEFT, text=localization.GetByLabel('UI/Fitting/FittingWindow/Defense'), padTop=offenceValue.height + 4)
    227         defenseValue = TextBody(parent=left, align=Align.TOPLEFT, text=localization.GetByLabel('UI/Fitting/FittingWindow/ColoredEffectiveHp', effectiveHp=int(self.fitting_controller.GetEffectiveHp().value)), color=TextColor.SECONDARY, padTop=defenseLabel.height)
    228         alignLabel = TextBody(parent=left, align=Align.TOPLEFT, text=localization.GetByLabel('Tooltips/FittingWindow/AlignTime'), padTop=defenseValue.height + 4)
    229         TextBody(parent=left, align=Align.TOPLEFT, text=localization.GetByLabel('UI/Fitting/FittingWindow/AlignTime', value=self.fitting_controller.GetAlignTime().value), color=TextColor.SECONDARY, padTop=alignLabel.height)
    230         self.capCont = ContainerAutoSize(name='capCont', parent=right, align=Align.CENTERBOTTOM)
    231         self.construct_power_core_cont(self.capCont)
    232         self.construct_cap_labels(self.capCont)
    233         self.update_capacitor_stats()
    234 
    235     def construct_power_core_cont(self, parent):
    236         sizeWrapper = Container(name='powerCoreWrapper', parent=parent, align=Align.CENTERLEFT, width=50, height=50)
    237         self.powerCoreCont = Container(parent=sizeWrapper, name='powercore', pos=(0, 0, 50, 50), align=uiconst.CENTER, state=uiconst.UI_DISABLED)
    238 
    239     def construct_cap_labels(self, parent):
    240         textCont = ContainerAutoSize(name='textCont', parent=parent, align=uiconst.CENTERLEFT, padLeft=60)
    241         self.capStateLabel = TextBody(text='-', parent=textCont, state=uiconst.UI_NORMAL, align=uiconst.TOPLEFT)
    242         self.capAndRechargeLabel = TextBody(text='-', parent=textCont, state=uiconst.UI_NORMAL, align=uiconst.TOPLEFT, padTop=self.capStateLabel.height, color=TextColor.SECONDARY)
    243         self.capAndRechargeLabel.hint = GetByLabel('UI/Fitting/FittingWindow/CapacityAndRechargeRate')
    244         self.deltaLabel = TextBody(text='-', parent=textCont, state=uiconst.UI_NORMAL, align=uiconst.TOPLEFT, padTop=self.capAndRechargeLabel.height, color=TextColor.SECONDARY)
    245         self.deltaLabel.hint = GetByLabel('UI/Fitting/FittingWindow/ExcessCapacitor')
    246 
    247     def update_capacitor_stats(self):
    248         deltaInfo, deltaPercentageInfo, loadBalanceInfo, ttlInfo = self.fitting_controller.GetCapSimulatorInfos()
    249         self._set_capacitor_header_text(ttlInfo.value, loadBalanceInfo.value)
    250         self._set_capacitor_recharge_text()
    251         self._set_capacitor_delta_text(deltaInfo, deltaPercentageInfo)
    252         self._redraw_power_core()
    253         self._color_power_core(self.powerUnits, loadBalanceInfo.value, ttlInfo.value)
    254 
    255     def _set_capacitor_header_text(self, TTL, loadBalance):
    256         if loadBalance > 0:
    257             sustainableText = eveformat.color(GetByLabel('UI/Fitting/FittingWindow/Stable'), color=eveColor.SUCCESS_GREEN)
    258             capHint = GetByLabel('UI/Fitting/FittingWindow/ModulesSustainable')
    259         else:
    260             sustainableText = eveformat.color(GetByLabel('UI/Fitting/FittingWindow/CapacitorNotStable', time=long(TTL)), color=eveColor.DANGER_RED)
    261             capHint = GetByLabel('UI/Fitting/FittingWindow/ModulesNotSustainable')
    262         self.capStateLabel.text = GetByLabel('UI/InfoWindow/CapacitorStatus', status=sustainableText)
    263         if capHint:
    264             self.capStateLabel.hint = capHint
    265 
    266     def _set_capacitor_recharge_text(self):
    267         maxCapInfo = self.fitting_controller.GetCapacitorCapacity()
    268         maxCapFormattedValue = GetFormatAndValue(self.capCapacityAttribute, int(maxCapInfo.value))
    269         maxCapText = GetColoredText(isBetter=maxCapInfo.isBetterThanBefore, text=maxCapFormattedValue)
    270         rechargeRate = self.fitting_controller.GetCapRechargeRate()
    271         rechargeFormattedValue = GetFormatAndValue(self.rechargeRateAttribute, int(rechargeRate.value))
    272         rechargeRateText = GetColoredText(isBetter=rechargeRate.isBetterThanBefore, text=rechargeFormattedValue)
    273         capText = GetByLabel('UI/Fitting/FittingWindow/CapacitorCapAndRechargeTime', capacitorCapacity=maxCapText, capacitorRechargeTime=rechargeRateText, startColorTag1='', startColorTag2='', endColorTag='')
    274         self.capAndRechargeLabel.text = capText
    275 
    276     def _set_capacitor_delta_text(self, deltaInfo, deltaPercentageInfo):
    277         deltaText = GetByLabel('UI/Fitting/FittingWindow/CapacitorDelta', delta=deltaInfo.value, percentage=deltaPercentageInfo.value)
    278         deltaText = GetColoredText(isBetter=deltaInfo.isBetterThanBefore, text=deltaText)
    279         self.deltaLabel.text = deltaText
    280 
    281     def _redraw_power_core(self):
    282 
    283         def _add_power_column(parent, colIdx, rotationStep, columnWidth, columnHeight):
    284             rotation = math.radians(colIdx * -rotationStep)
    285             powerColumn = Transform(parent=parent, name='powerColumn', pos=(0,
    286              0,
    287              columnWidth,
    288              columnHeight), align=uiconst.CENTER, state=uiconst.UI_DISABLED, rotation=rotation, idx=0)
    289             return powerColumn
    290 
    291         def _add_power_cell(parent, cellIdx):
    292             newcell = Sprite(parent=parent, name='pmark', pos=(0,
    293              cellIdx * 6,
    294              12 - cellIdx * 4,
    295              7), align=uiconst.CENTERTOP, state=uiconst.UI_DISABLED, texturePath='res:/UI/Texture/classes/ShipUI/capacitorCell.png', color=(0.94, 0.35, 0.19, 1.0), idx=0, blendMode=trinity.TR2_SBM_ADD)
    296             return newcell
    297 
    298         self.powerCoreCont.Flush()
    299         maxCapInfo = self.fitting_controller.GetCapacitorCapacity()
    300         numcol = min(10, int(maxCapInfo.value / 50))
    301         rotStep = 360.0 / max(1, numcol)
    302         colWidth = max(12, min(16, numcol and int(192 / numcol)))
    303         colHeight = self.powerCoreCont.height
    304         self.powerUnits = []
    305         for colIndex in xrange(numcol):
    306             column = _add_power_column(self.powerCoreCont, colIndex, rotStep, colWidth, colHeight)
    307             for cellIndex in xrange(3):
    308                 powerCell = _add_power_cell(column, cellIndex)
    309                 self.powerUnits.insert(0, powerCell)
    310 
    311         self.maxcap = maxCapInfo.value
    312 
    313     def _color_power_core(self, powerUnits, loadBalance, TTL):
    314 
    315         def ScaleColor(colorTuple, scale):
    316             return Color(colorTuple[0] * scale, colorTuple[1] * scale, colorTuple[2] * scale)
    317 
    318         badColor = ScaleColor(BAD_COLOR, 1.0 - loadBalance)
    319         goodColor = ScaleColor(GOOD_COLOR, loadBalance)
    320         visible = max(0, min(len(powerUnits), int(loadBalance * len(powerUnits))))
    321         for cellIdx, each in enumerate(powerUnits):
    322             if cellIdx >= visible:
    323                 each.SetRGBA(*EMPTY_COLOR)
    324             else:
    325                 r = badColor.r + goodColor.r
    326                 g = badColor.g + goodColor.g
    327                 b = badColor.b + goodColor.b
    328                 each.SetRGBA(r, g, b, 1.0)
    329 
    330         if loadBalance == 0:
    331             hint = GetByLabel('UI/Fitting/FittingWindow/CapRunsOutBy', ttl=long(TTL))
    332         else:
    333             hint = GetByLabel('UI/Fitting/FittingWindow/CapSustainableBy', balance=loadBalance * 100)
    334         self.hint = hint
    335 
    336     def _enable_expanded_view(self):
    337         self.attributeCont.SetParent(self.scrollContainer)
    338 
    339     def _enable_minimized_view(self):
    340         self.attributeCont.SetParent(self.content_scroll_minimized)
    341 
    342     @classmethod
    343     def get_name(cls):
    344         return localization.GetByLabel('UI/InfoWindow/TabNames/Fitting')
    345 
    346     @classmethod
    347     def get_icon(cls):
    348         return eveicon.fitting
    349 
    350     @classmethod
    351     def is_visible(cls, typeID, itemID = None, rec = None):
    352         groupID = evetypes.GetGroupID(typeID)
    353         return groupID not in (const.groupCapsule, const.groupShuttle)
    354 
    355     def get_camera_position(self):
    356         return ANGLE_FITTING
    357 
    358     def get_tab_type(self):
    359         return TAB_FITTING
    360 
    361     def _get_slots_used_and_total_text(self, used, total):
    362         if total == 0:
    363             return used
    364         return GetByLabel('UI/InfoWindow/FittingSlotsUsedAndTotal', usedSlots=used, slotCount=total)
    365 
    366     def GetFittingSlotEntry(self, slot_info, module_count):
    367         slot_count = self.GetTotalSlotCount(slot_info)
    368         if slot_count <= 0 and module_count <= 0:
    369             return
    370         is_my_active_ship = self.itemID is not None and eveCfg.GetActiveShip() == self.itemID
    371         is_owned_by_me = self.item is not None and self.item.ownerID == session.charid
    372         if is_my_active_ship or is_owned_by_me:
    373             return AsFittingEntry(ListEntryFittingSlot, label=self.GetFittingSlotEntryLabel(slot_info), text=self._get_slots_used_and_total_text(module_count, slot_count), iconID=GetIconFile(dogma.data.get_attribute_icon_id(slot_info['attributeID'])))
    374         else:
    375             return AsFittingEntry(ListEntry, label=self.GetFittingSlotEntryLabel(slot_info), text=FmtAmt(slot_count), iconID=GetIconFile(dogma.data.get_attribute_icon_id(slot_info['attributeID'])))
    376 
    377     def GetFittingEntry(self, typeID, quantity, isCharge = False):
    378         itemName = evetypes.GetName(typeID)
    379         if quantity > 1:
    380             text = GetByLabel('UI/InfoWindow/FittingItemLabelWithQuantity', quantity=quantity, itemName=itemName)
    381         else:
    382             text = itemName
    383         return AsFittingEntry(ListEntryEveType, typeID=typeID, text=text, indent=16 if isCharge else 0)
    384 
    385     def GetHardpointsEntry(self, turretSlotsUsed, launcherSlotsUsed, parent, align = Align.TOTOP):
    386         turretSlotCount = self.GetAttributeValue(const.attributeTurretSlotsLeft)
    387         launcherSlotCount = self.GetAttributeValue(const.attributeLauncherSlotsLeft)
    388         if self.itemID == eveCfg.GetActiveShip():
    389             turretSlotCount += turretSlotsUsed
    390             launcherSlotCount += launcherSlotsUsed
    391         if turretSlotCount <= 0 and launcherSlotCount <= 0:
    392             return None
    393         return ListEntryWeaponSlots(parent=parent, align=align, turretSlots=turretSlotCount, turretSlotsUsed=turretSlotsUsed, launcherSlots=launcherSlotCount, launcherSlotsUsed=launcherSlotsUsed)
    394 
    395     def GetDroneEntries(self, items):
    396         if not idCheckers.IsShipType(self.typeID):
    397             return []
    398         totalDroneCapacity = dogma.data.get_type_attribute(self.typeID, const.attributeDroneCapacity)
    399         if not totalDroneCapacity:
    400             return []
    401         dronesByType = collections.defaultdict(int)
    402         for item in items:
    403             if item.flagID == const.flagDroneBay:
    404                 dronesByType[item.typeID] += item.stacksize
    405 
    406         usedCapacity = sum([ evetypes.GetVolume(typeID) * amount for typeID, amount in dronesByType.iteritems() ])
    407         isMyActiveShip = self.itemID is not None and eveCfg.GetActiveShip() == self.itemID
    408         isOwnedByMe = self.item is not None and self.item.ownerID == session.charid
    409         if isMyActiveShip or isOwnedByMe:
    410             headerClass = ListEntryFittingSlot
    411             headerText = GetByLabel('UI/Inventory/ContainerQuantityAndCapacity', quantity=usedCapacity, capacity=totalDroneCapacity)
    412         else:
    413             headerClass = ListEntry
    414             headerText = GetByLabel('UI/Inventory/ContainerCapacity', capacity=totalDroneCapacity)
    415         dronesHeaderEntry = AsFittingEntry(headerClass, label=GetByLabel('UI/Drones/Drones'), text=headerText, iconID=eveicon.drones)
    416         result = [dronesHeaderEntry]
    417         result.extend([ self.GetFittingEntry(typeID, quantity) for typeID, quantity in sorted(dronesByType.items(), key=lambda d: evetypes.GetName(d[0])) ])
    418         return result
    419 
    420     def GetTotalSlotCount(self, slotInfo):
    421         if slotInfo['attributeID'] == const.attributeUpgradeSlotsLeft:
    422             return self.GetTypeAttributeValue(slotInfo['attributeID'])
    423         else:
    424             return self.GetAttributeValue(slotInfo['attributeID'])
    425 
    426     def GetFittingSlotEntryLabel(self, slotInfo):
    427         if slotInfo['attributeID'] == const.attributeUpgradeSlotsLeft:
    428             rigSize = self.GetAttributeValue(const.attributeRigSize)
    429             formattedRigSize = GetFormattedAttributeAndValue(const.attributeRigSize, rigSize)
    430             return GetByLabel(slotInfo['label'], rigSize=getattr(formattedRigSize, 'value', ''))
    431         return GetByLabel(slotInfo['label'])
    432 
    433     def GetAttributeValue(self, attributeID):
    434         dogmaLocation = sm.GetService('clientDogmaIM').GetDogmaLocation()
    435         if dogmaLocation.IsItemLoaded(self.itemID):
    436             return dogmaLocation.GetAccurateAttributeValue(self.itemID, attributeID)
    437         return self.GetTypeAttributeValue(attributeID)
    438 
    439     def GetTypeAttributeValue(self, attributeID):
    440         info = sm.GetService('godma').GetStateManager().GetShipType(self.typeID)
    441         return getattr(info, dogma.data.get_attribute_name(attributeID))
    442 
    443     def get_sub_systems_by_group(self):
    444         godma = sm.StartService('godma')
    445         subSystemsByGroupID = defaultdict(list)
    446         for groupID in evetypes.GetGroupIDsByCategory(const.categorySubSystem):
    447             for typeID in evetypes.GetTypeIDsByGroup(groupID):
    448                 if not evetypes.IsPublished(typeID):
    449                     continue
    450                 if godma.GetTypeAttribute(typeID, const.attributeFitsToShipType) != self.typeID:
    451                     continue
    452                 if not IsSubsystemFlagVisible(int(godma.GetTypeAttribute(typeID, const.attributeSubSystemSlot))):
    453                     continue
    454                 subSystemsByGroupID[groupID].append(typeID)
    455 
    456         return subSystemsByGroupID
    457 
    458     def GetFittingDragData(self):
    459         fitting = self._controller.get_fitting_data()
    460         entry = KeyVal()
    461         entry.fitting = fitting
    462         entry.label = fitting.name
    463         entry.displayText = fitting.name
    464         entry.__guid__ = 'listentry.FittingEntry'
    465         return [entry]
    466 
    467     def OnFittingIconClicked(self, *args):
    468         fitting = self._controller.get_fitting_data()
    469         windowID = 'Save_ViewFitting_%s' % fitting.shipTypeID
    470         wnd = ViewFitting.GetIfOpen(windowID=windowID)
    471         if wnd and not wnd.destroyed:
    472             wnd.ReloadWnd(windowID, fitting, truncated=None)
    473             wnd.Maximize()
    474         else:
    475             ViewFitting.Open(windowID=windowID, fitting=fitting, truncated=None)
    476 
    477     def OnSimulateClicked(self, *args):
    478         self._controller.open_simulation()
    479 
    480 
    481 def ConstructFittingEntry(entryData, parent, align = Align.TOTOP):
    482     entryClass, label, text, typeID, iconID, getIcon, indent = entryData
    483     return entryClass(label=label, text=text, typeID=typeID, texturePath=iconID, getIcon=getIcon, indent=indent, parent=parent, align=align)
    484 
    485 
    486 def AsFittingEntry(entryClass, label = None, text = None, typeID = None, iconID = None, getIcon = False, indent = 0):
    487     return (entryClass,
    488      label,
    489      text,
    490      typeID,
    491      iconID,
    492      getIcon,
    493      indent)
    494 
    495 
    496 def IsCharge(item):
    497     return item.categoryID == const.categoryCharge
    498 
    499 
    500 def HasEffect(typeID, effectID):
    501     return sm.GetService('clientDogmaStaticSvc').TypeHasEffect(typeID, effectID)
    502 
    503 
    504 class ListEntryFittingSlot(_ListEntryBase):
    505     default_bgColor = (1, 1, 1, 0.05)
    506 
    507     def __init__(self, label = None, text = None, texturePath = None, indent = 0, **kw):
    508         self.label = label
    509         self.text = text
    510         self.texturePath = texturePath
    511         super(ListEntryFittingSlot, self).__init__(indent, **kw)
    512 
    513     def construct_layout(self):
    514         super(ListEntryFittingSlot, self).construct_layout()
    515         iconCont = Container(parent=self.content, align=Align.TOLEFT, width=24, padLeft=8, padRight=8)
    516         Sprite(parent=iconCont, align=Align.CENTER, texturePath=self.texturePath, width=24, height=24, color=TextColor.SECONDARY)
    517         textCont = ContainerAutoSize(parent=self.content, align=Align.TORIGHT, padRight=8)
    518         TextBody(parent=textCont, text=self.text, align=Align.CENTERRIGHT)
    519         labelCont = Container(parent=self.content, align=Align.TOALL, clipChildren=True, padRight=4)
    520         TextBody(parent=labelCont, align=Align.CENTERLEFT, text=self.label, autoFadeSides=16)
    521 
    522 
    523 class ListEntryWeaponSlots(_ListEntryBase):
    524 
    525     def __init__(self, turretSlots, turretSlotsUsed, launcherSlots, launcherSlotsUsed, **kw):
    526         self.turretSlots = turretSlots
    527         self.turretSlotsUsed = turretSlotsUsed
    528         self.launcherSlots = launcherSlots
    529         self.launcherSlotsUsed = launcherSlotsUsed
    530         super(ListEntryWeaponSlots, self).__init__(indent=0, **kw)
    531 
    532     def construct_layout(self):
    533         super(ListEntryWeaponSlots, self).construct_layout()
    534         turretTopContainer = Container(parent=self.content, align=Align.TOLEFT_PROP, width=0.5, state=uiconst.UI_NORMAL)
    535         turretTopContainer.LoadTooltipPanel = self.LoadTooltipPanelForTurret
    536         Sprite(name='turretHardpointsIcon', texturePath='res:/UI/Texture/Icons/26_64_1.png', parent=turretTopContainer, align=uiconst.TOPLEFT, pos=(1, 2, 24, 24), ignoreSize=True, state=uiconst.UI_DISABLED)
    537         self.turretBubbles = []
    538         for i in range(8):
    539             x = i * 14 + 26
    540             bubble = Sprite(parent=turretTopContainer, pos=(x,
    541              7,
    542              14,
    543              16), useSizeFromTexture=True, align=uiconst.TOPLEFT, state=uiconst.UI_DISABLED)
    544             self.turretBubbles.append(bubble)
    545 
    546         launcherTopContainer = Container(parent=self.content, align=uiconst.TOLEFT_PROP, width=0.5, state=uiconst.UI_NORMAL)
    547         launcherTopContainer.LoadTooltipPanel = self.LoadTooltipPanelForLauncher
    548         Sprite(name='launcherHardpointsIcon', texturePath='res:/UI/Texture/Icons/81_64_16.png', parent=launcherTopContainer, align=uiconst.TOPRIGHT, pos=(1, 2, 24, 24), ignoreSize=True, state=uiconst.UI_DISABLED)
    549         self.launcherBubbles = []
    550         for i in range(8):
    551             x = i * 14 + 26
    552             bubble = Sprite(parent=launcherTopContainer, pos=(x,
    553              7,
    554              14,
    555              16), useSizeFromTexture=True, align=uiconst.TOPRIGHT, state=uiconst.UI_DISABLED)
    556             self.launcherBubbles.append(bubble)
    557 
    558         self.FillBubbles(self.turretBubbles, self.turretSlotsUsed, self.turretSlots)
    559         self.FillBubbles(self.launcherBubbles, self.launcherSlotsUsed, self.launcherSlots)
    560 
    561     def FillBubbles(self, bubbles, slotsUsed, slotCount):
    562         for i, bubble in enumerate(bubbles):
    563             if i < slotsUsed:
    564                 bubble.display = True
    565                 bubble.texturePath = 'res:/UI/Texture/classes/Fitting/slotTaken.png'
    566             elif i < slotCount:
    567                 bubble.display = True
    568                 bubble.texturePath = 'res:/UI/Texture/classes/Fitting/slotLeft.png'
    569             else:
    570                 bubble.display = False
    571 
    572     def LoadTooltipPanelForTurret(self, tooltipPanel, *args):
    573         counterText = localization.GetByLabel('Tooltips/FittingWindow/TurretHardPointBubbles_description', hardpointsUsed=self.turretSlotsUsed, hardpointsTotal=self.turretSlots)
    574         return self.LoadTooltipPanelForTurretsAndLaunchers(tooltipPanel, const.attributeTurretSlotsLeft, counterText)
    575 
    576     def LoadTooltipPanelForLauncher(self, tooltipPanel, *args):
    577         counterText = localization.GetByLabel('Tooltips/FittingWindow/LauncherHardPointBubbles_description', hardpointsUsed=self.launcherSlotsUsed, hardpointsTotal=self.launcherSlots)
    578         return self.LoadTooltipPanelForTurretsAndLaunchers(tooltipPanel, const.attributeLauncherSlotsLeft, counterText)
    579 
    580     def LoadTooltipPanelForTurretsAndLaunchers(self, tooltipPanel, attributeID, counterText):
    581         attribute = dogma.data.get_attribute(attributeID)
    582         headerText = localization.GetByMessageID(attribute.tooltipTitleID)
    583         descriptionText = localization.GetByMessageID(attribute.tooltipDescriptionID)
    584         tooltipPanel.LoadGeneric2ColumnTemplate()
    585         tooltipPanel.AddLabelMedium(text=headerText, bold=True)
    586         tooltipPanel.AddLabelMedium(text=counterText, bold=True, align=uiconst.TOPRIGHT, cellPadding=(20, 0, 0, 0))
    587         tooltipPanel.AddLabelMedium(text=descriptionText, wrapWidth=200, colSpan=tooltipPanel.columns, color=(0.6, 0.6, 0.6, 1))
    588 
    589 
    590 class ListEntrySubsystem(ListEntryEveType):
    591 
    592     def LoadTooltipPanel(self, tooltip_panel, *args):
    593         tooltip_panel.LoadGeneric1ColumnTemplate()
    594         wrapper = Container(align=Align.CENTERTOP, width=300, height=450)
    595         traitCont = TraitsContainer(parent=wrapper, typeID=self._typeID, align=uiconst.TOTOP, traitAttributeIcons=False)
    596         wrapper.height = traitCont.height + 32
    597         tooltip_panel.AddCell(wrapper)