dronePickerMenu.py (16175B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\shared\fittingScreen\dronePickerMenu.py 2 import carbonui.const as uiconst 3 import uthread 4 from carbonui.control.scrollentries import SE_BaseClassCore 5 from carbonui.primitives.container import Container 6 from carbonui.primitives.sprite import Sprite 7 from eve.client.script.ui.control.entries.mouse_inside_scroll_entry import MouseInsideScrollEntry 8 from eve.client.script.ui.control.entries.util import GetFromClass 9 from eve.client.script.ui.control.eveLabel import EveLabelMedium, EveLabelLarge, EveLabelSmall 10 from eve.client.script.ui.control.eveWindowUnderlay import RaisedUnderlay 11 from eve.client.script.ui.control.themeColored import FillThemeColored 12 from eve.client.script.ui.shared.fitting.fittingUtil import EatSignalChangingErrors 13 from eve.client.script.ui.shared.fittingScreen.droneTooltip import DroneFittingTooltipWrapper 14 from eve.client.script.ui.shared.fittingScreen.itemPickerMenu import ItemPickerMenu, ItemPickerBase, GetMaxQty 15 from eve.common.lib import appConst as const 16 from localization import GetByLabel 17 from signals.signalUtil import ChangeSignalConnect 18 19 class ItemDronePicker(ItemPickerBase): 20 __guid__ = 'listentry.ItemDronePicker' 21 ENTRYHEIGHT = 64 22 ICONWIDTH = ENTRYHEIGHT - 4 23 NUM_CONT_WIDTH = 50 24 REMOVE_PADDING = 36 25 haveSkillsLabelPath = 'UI/Fitting/FittingWindow/HaveSkillsForDrone' 26 dontHaveSkillsLabelPath = 'UI/Fitting/FittingWindow/MissingSkillsForDrone' 27 28 def ApplyAttributes(self, attributes): 29 ItemPickerBase.ApplyAttributes(self, attributes) 30 self.tooltipPanelClassInfo = DroneFittingTooltipWrapper() 31 fromBottom = 15 32 self.selectedLabel = EveLabelSmall(text=GetByLabel('UI/Fitting/FittingWindow/SelectedDrones'), parent=self.restCont, left=0, top=fromBottom, state=uiconst.UI_DISABLED, color=None, maxLines=1, align=uiconst.BOTTOMLEFT) 33 self.selectedLabel.SetRGBA(0.75, 0.75, 0.75, 0.75) 34 left = self.selectedLabel.width + 2 35 self.dronePickingCont = DronePickingBoxCont(parent=self.restCont, align=uiconst.BOTTOMLEFT, top=fromBottom, left=left, boxClickedFunc=self.OnBoxClicked) 36 self.label.top = 10 37 self.label.SetAlign(uiconst.TOPLEFT) 38 if self.sr.node.get('isDynamic'): 39 self.numItemsCont.Hide() 40 41 def Load(self, node): 42 ItemPickerBase.Load(self, node) 43 self.dronePickingCont.LoadBoxes(node) 44 45 def OnBoxClicked(self, box): 46 if self.changeActive: 47 numWanted = box.boxIdx + 1 48 self.changeActive(self.sr.node, numWanted) 49 50 @classmethod 51 def GetEntryWidth(cls, node): 52 maxActiveDrones = node.get('maxActiveDrones', 5) 53 label = node['label'] 54 textwidth, _ = EveLabelMedium.MeasureTextSize(label, maxLines=1) 55 selectedTw, _ = EveLabelSmall.MeasureTextSize(GetByLabel('UI/Fitting/FittingWindow/SelectedDrones'), maxLines=1) 56 boxContWidth = (DronePickingBoxCont.boxSize + DronePickingBoxCont.boxPadding) * maxActiveDrones 57 centerWidth = max(textwidth, selectedTw + boxContWidth) 58 w = cls.NUM_CONT_WIDTH + cls.ENTRYHEIGHT + centerWidth + cls.REMOVE_PADDING + 40 59 return w 60 61 def GetHeight(self, *args): 62 node, width = args 63 node.height = ItemDronePicker.ENTRYHEIGHT 64 return node.height 65 66 def GetDroneActivityDescription(self): 67 return '' 68 69 def GetDroneStateDescription(self): 70 return '' 71 72 73 class DronePickerMenu(ItemPickerMenu): 74 entryClass = ItemDronePicker 75 emptyLabelPath = 'UI/Fitting/FittingWindow/NoDronesSimulated' 76 flagID = const.flagDroneBay 77 78 def __init__(self, controller, menuParent): 79 ItemPickerMenu.__init__(self, controller, menuParent) 80 self.controller = controller 81 self.ChangeSignalConnection(connect=True) 82 83 def ChangeSignalConnection(self, connect = True): 84 signalAndCallback = [(self.controller.on_warning_display_changed, self.HiliteProblematicEntries)] 85 ChangeSignalConnect(signalAndCallback, connect) 86 87 def DisconnectController(self): 88 with EatSignalChangingErrors(errorMsg='DronePickerMenu'): 89 self.ChangeSignalConnection(connect=False) 90 self.controller = False 91 92 def GetItems(self): 93 shipDogmaItem = self.fittingDogmaLocation.GetShip() 94 if shipDogmaItem: 95 return shipDogmaItem.GetDrones() 96 else: 97 return {} 98 99 def GetHeaderEntry(self, activeItemsSet): 100 allDroneIDs = set(self.GetItems().keys()) 101 numActive = len(allDroneIDs.intersection(activeItemsSet)) 102 numSelectable = self.GetMaxActiveDrones() 103 entry = GetFromClass(DronePickingHeader, {'itemIDs': set(allDroneIDs), 104 'numActive': numActive, 105 'numSelectable': numSelectable}) 106 return entry 107 108 def GetExtraData(self): 109 activeItemsCopy = self.GetActiveItems() 110 totalCanActivate = self.GetTotalCanActivate(len(activeItemsCopy)) 111 maxActiveDrones = self.GetMaxActiveDrones() 112 bandwidthLeft = self.GetBandwidthLeft() 113 extraData = {'totalCanActivate': totalCanActivate, 114 'bandwidthLeft': bandwidthLeft, 115 'maxActiveDrones': maxActiveDrones} 116 return extraData 117 118 def GetExtraDataForType(self, itemIDs, activeItemsSet, typeID): 119 activeOfType = activeItemsSet.intersection(itemIDs) 120 numActive = len(activeOfType) 121 dynamicItemSvc = sm.GetService('dynamicItemSvc') 122 if dynamicItemSvc.IsDynamicItem(typeID): 123 attributes = dynamicItemSvc.GetDynamicItemAttributes(list(itemIDs)[0]) 124 droneBandwidth = attributes[const.attributeDroneBandwidthUsed] 125 else: 126 droneBandwidth = self.fittingDogmaLocation.dogmaStaticMgr.GetTypeAttribute(typeID, const.attributeDroneBandwidthUsed) 127 return {'numActive': numActive, 128 'droneBandwidth': droneBandwidth, 129 'isDynamic': dynamicItemSvc.IsDynamicItem(typeID)} 130 131 def GetExtraDataFuncs(self): 132 extraDataFunc = {'changeActive': self.ChangeActiveNum, 133 'addToActive': self.AddDroneToActive, 134 'changeNum': self.ChangeNumDrones, 135 'removeFromActive': self.RemoveDroneFromActive} 136 return extraDataFunc 137 138 def GetTotalCanActivate(self, numActive): 139 return max(0, self.GetMaxActiveDrones() - numActive) 140 141 def RemoveItem(self, node): 142 typeID = node.typeID 143 itemID = list(node.itemIDs)[0] 144 dynamicItemSvc = sm.GetService('dynamicItemSvc') 145 allDrones = self.GetItems() 146 allDronesOfType = [ droneID for droneID, d in allDrones.iteritems() if d.itemID == itemID or d.typeID == typeID and not dynamicItemSvc.IsDynamicItem(typeID) ] 147 sm.GetService('ghostFittingSvc').UnfitDrones(allDronesOfType) 148 self.LoadItemScroll() 149 150 def ChangeActiveNum(self, node, wantNumActive): 151 itemIDs = node.itemIDs 152 activeOfType = self.GetActiveOfType(itemIDs) 153 if len(activeOfType) == wantNumActive: 154 wantNumActive = max(0, wantNumActive - 1) 155 diff = wantNumActive - len(activeOfType) 156 if activeOfType and diff < 0: 157 numToRemove = -diff 158 self.RemoveDroneFromActive(activeOfType, numToRemove) 159 elif diff > 0: 160 self.AddDroneToActive(itemIDs, numToAdd=diff) 161 162 def GetActiveOfType(self, itemIDs): 163 activeOfType = itemIDs.intersection(self.GetActiveItems()) 164 return activeOfType 165 166 def AddDroneToActive(self, itemIDs, numToAdd = 1): 167 activeOfType = self.GetActiveOfType(itemIDs) 168 notActiveItemIDs = itemIDs.difference(activeOfType) 169 try: 170 for i, eachItemID in enumerate(notActiveItemIDs): 171 if i >= numToAdd: 172 break 173 self.fittingDogmaLocation.SetDroneActivityState(eachItemID, setActive=True) 174 175 finally: 176 sm.GetService('ghostFittingSvc').SendOnStatsUpdatedEvent() 177 self.UpdateNodes() 178 179 def RemoveDroneFromActive(self, itemIDs, numToRemove = 1): 180 activeOfType = self.GetActiveOfType(itemIDs) 181 try: 182 for i, eachItemID in enumerate(list(activeOfType)): 183 if i >= numToRemove: 184 break 185 self.fittingDogmaLocation.SetDroneActivityState(eachItemID, setActive=False) 186 187 finally: 188 sm.GetService('ghostFittingSvc').SendOnStatsUpdatedEvent() 189 self.UpdateNodes() 190 191 def ChangeNumDrones(self, typeID, numWanted): 192 allDrones = self.GetItems() 193 allDronesOfType = [ droneID for droneID, d in allDrones.iteritems() if d.typeID == typeID ] 194 ghostFittingSvc = sm.GetService('ghostFittingSvc') 195 diff = numWanted - len(allDronesOfType) 196 if diff > 0: 197 ghostFittingSvc.TryFitDronesToDroneBay({(typeID, None): diff}) 198 elif diff < 0: 199 activeDrones = self.GetActiveItems() 200 allDronesOfType.sort(key=lambda x: x in activeDrones) 201 dronesToUnfit = allDronesOfType[:-diff] 202 ghostFittingSvc.UnfitDrones(dronesToUnfit) 203 else: 204 return 205 uthread.new(self.UpdateNodes) 206 207 def GetActiveItems(self): 208 activeItemsCopy = self.fittingDogmaLocation.GetActiveDrones() 209 return activeItemsCopy 210 211 def GetMaxActiveDrones(self): 212 return max(5, int(self.fittingDogmaLocation.GetMaxActiveDrones())) 213 214 def GetBandwidthLeft(self): 215 shipID = self.fittingDogmaLocation.GetCurrentShipID() 216 activeDrones = self.fittingDogmaLocation.GetActiveDrones() 217 from shipfitting.droneUtil import GetDroneBandwidth 218 bandwidthUsed, shipBandwith = GetDroneBandwidth(shipID, self.fittingDogmaLocation, activeDrones) 219 return shipBandwith - bandwidthUsed 220 221 def HiliteProblematicEntries(self, warningSlotDict): 222 warningLevel = warningSlotDict.get(self.flagID, None) 223 self.UpdateProblematicEntries(warningLevel) 224 225 def UpdateNodes(self): 226 allItems = self.GetItems() 227 activeItemsSet = set(self.GetActiveItems()) 228 extraData = self.GetExtraData() 229 dynamicItemSvc = sm.GetService('dynamicItemSvc') 230 for eachNode in self.scroll.GetNodes(): 231 typeID = eachNode.typeID 232 if eachNode.decoClass == DronePickingHeader: 233 eachNode.numActive = len(activeItemsSet) 234 eachNode.itemIDs = set(self.GetItems().keys()) 235 continue 236 if eachNode.itemIDs: 237 itemID = list(eachNode.itemIDs)[0] 238 else: 239 itemID = None 240 allItemsOfType = {eachItemID:item for eachItemID, item in allItems.iteritems() if item.itemID == itemID or item.typeID == typeID and not dynamicItemSvc.IsDynamicItem(item.typeID)} 241 itemIDs = set(allItemsOfType.keys()) 242 eachNode.itemIDs = itemIDs 243 currentQty = sum((v.stacksize for v in allItemsOfType.itervalues())) 244 maxExtraQty = GetMaxQty(self.fittingDogmaLocation, const.flagDroneBay, {typeID: 1}) or 0 245 eachNode.intRange = (0, currentQty + maxExtraQty) 246 eachNode.qty = currentQty 247 eachNode.update(extraData) 248 extraDataForType = self.GetExtraDataForType(itemIDs, activeItemsSet, typeID) 249 eachNode.update(extraDataForType) 250 251 for eachNode in self.scroll.GetNodes(): 252 if eachNode.panel: 253 eachNode.panel.Load(eachNode) 254 255 256 class DronePickingHeader(MouseInsideScrollEntry): 257 258 def ApplyAttributes(self, attributes): 259 SE_BaseClassCore.ApplyAttributes(self, attributes) 260 self.label = EveLabelLarge(text='', parent=self, left=0, top=0, state=uiconst.UI_DISABLED, color=None, maxLines=1, align=uiconst.TOPLEFT) 261 262 def Load(self, node): 263 selectable = node.numSelectable 264 self.label.text = GetByLabel('UI/Fitting/FittingWindow/NumActiveDrones', numActive=node.numActive, numSelectable=selectable) 265 266 267 class DronePickingBoxCont(Container): 268 boxSize = 14 269 boxPadding = 1 270 271 def ApplyAttributes(self, attributes): 272 Container.ApplyAttributes(self, attributes) 273 self.boxClickedFunc = attributes.boxClickedFunc 274 self.boxes = [] 275 276 def LoadBoxes(self, node): 277 boxNum = node.maxActiveDrones 278 if node.isDynamic: 279 boxNum = 1 280 self.Flush() 281 self.boxes = [] 282 for i in xrange(boxNum): 283 b = DroneBox(parent=self, padLeft=self.boxPadding, padRight=self.boxPadding, pos=(0, 284 0, 285 self.boxSize, 286 0), align=uiconst.TOLEFT, state=uiconst.UI_NORMAL, boxIdx=i) 287 b.OnMouseEnter = (self.OnMouseEnterBox, i) 288 b.OnMouseExit = (self.OnMouseEnterBox, None) 289 b.OnClick = (self.OnClickBox, b) 290 self.boxes.append(b) 291 292 contWidth = boxNum * (self.boxSize + self.boxPadding * 2) 293 self.width = contWidth 294 self.height = self.boxSize 295 self.SetNumActive(node) 296 297 def OnMouseEnterBox(self, boxIdx): 298 for b in self.boxes: 299 if boxIdx is not None and b.boxIdx <= boxIdx and b.isSelectable: 300 b.underlay.OnMouseEnter() 301 else: 302 b.underlay.OnMouseExit() 303 304 def SetNumActive(self, node): 305 numTotalThisType = len(node.itemIDs) 306 numActive = node.numActive 307 totalCanActivate = node.totalCanActivate 308 bandwidthLeft = node.bandwidthLeft 309 droneBandwidth = node.droneBandwidth 310 maxActiveDrones = node.maxActiveDrones 311 numAfterActive = 0 312 for i, b in enumerate(self.boxes): 313 if i >= numTotalThisType: 314 b.SetUnavailableNoMoreItems() 315 continue 316 if i >= numActive: 317 numAfterActive += 1 318 if numAfterActive <= totalCanActivate: 319 if numAfterActive * droneBandwidth <= bandwidthLeft: 320 b.SetAvailableForSelection() 321 else: 322 b.SetUnavailableBandwidth() 323 else: 324 b.SetUnavailableTooManyActive(maxActiveDrones) 325 else: 326 b.SetSelected() 327 328 def OnClickBox(self, box): 329 if self.boxClickedFunc: 330 self.boxClickedFunc(box) 331 332 333 class DroneBox(Container): 334 335 def ApplyAttributes(self, attributes): 336 Container.ApplyAttributes(self, attributes) 337 boxIdx = attributes.boxIdx 338 self.isSelectable = False 339 self.boxIdx = boxIdx 340 self.checkMark = Container(parent=self, align=uiconst.TOALL) 341 texturePath = 'res:/UI/Texture/Shared/checkboxChecked.png' 342 checkMark = Sprite(parent=self.checkMark, pos=(0, 0, 16, 16), name='self_ok', state=uiconst.UI_DISABLED, texturePath=texturePath, align=uiconst.CENTER) 343 fill = FillThemeColored(parent=self.checkMark, padding=0, colorType=uiconst.COLORTYPE_UIHILIGHT) 344 self.checkMark.opacity = 1.0 345 self.checkMark.display = False 346 self.underlay = DroneSelectorUnderlay(parent=self, padding=0) 347 348 def SetUnavailableNoMoreItems(self): 349 self.opacity = 0.1 350 self.checkMark.display = False 351 self.isSelectable = False 352 self.hint = '' 353 354 def SetUnavailableCantSelect(self): 355 self.SetOpacity(0.5) 356 self.checkMark.display = False 357 self.isSelectable = False 358 359 def SetUnavailableTooManyActive(self, maxDrones): 360 self.SetUnavailableCantSelect() 361 self.hint = GetByLabel('UI/Fitting/FittingWindow/DroneCannotBeSelectedHint', numActive=maxDrones) 362 363 def SetUnavailableBandwidth(self): 364 self.SetUnavailableCantSelect() 365 self.hint = GetByLabel('UI/Fitting/FittingWindow/DroneCannotBeSelectedBandwidthHint') 366 367 def SetAvailableForSelection(self): 368 self.SetOpacity(1.0) 369 self.checkMark.display = False 370 self.isSelectable = True 371 self.hint = GetByLabel('UI/Fitting/FittingWindow/SelectDroneHint') 372 373 def SetSelected(self): 374 self.SetOpacity(1.0) 375 self.checkMark.display = True 376 self.isSelectable = True 377 self.hint = '' 378 379 380 class DroneSelectorUnderlay(RaisedUnderlay): 381 OPACITY_IDLE = 0.4 382 OPACITY_HOVER = 1.0 383 OPACITY_SELECTED = 1.0