fittingMgmtWindow.py (20382B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\shared\fittingMgmtWindow.py 2 import eveicon 3 import evetypes 4 import log 5 import uthread2 6 from carbon.common.script.sys.serviceConst import ROLE_WORLDMOD 7 from carbonui import AxisAlignment, TextAlign, TextColor, uiconst 8 from carbonui.control.combo import Combo 9 from carbonui.control.comboEntryData import ComboEntryData 10 from carbonui.control.contextMenu.menuData import MenuData 11 from carbonui.control.singlelineedits.singleLineEditText import SingleLineEditText 12 from carbonui.decorative.panelUnderlay import PanelUnderlay 13 from carbonui.primitives.container import Container 14 from carbonui.primitives.containerAutoSize import ContainerAutoSize 15 from carbonui.primitives.flowcontainer import FlowContainer 16 from carbonui.util.various_unsorted import SortListOfTuples 17 from eve.client.script.ui.control import eveScroll 18 from carbonui.button.menu import MenuButtonIcon 19 from eve.common.lib import appConst as const 20 from carbonui.control.buttonIcon import ButtonIcon 21 from carbonui.control.button import Button 22 from eve.client.script.ui.control.divider import Divider 23 from eve.client.script.ui.control.entries.fitting import FittingEntry 24 from eve.client.script.ui.control.entries.generic import Generic 25 from eve.client.script.ui.control.entries.header import Header 26 from eve.client.script.ui.control.entries.util import GetFromClass 27 from eve.client.script.ui.control.eveLabel import EveCaptionLarge 28 from carbonui.control.window import Window 29 from eve.client.script.ui.control.listgroup import ListGroup 30 from eve.client.script.ui.shared.export import ExportFittingsWindow, ImportFittingsWindow 31 from eve.client.script.ui.shared.fitting.fittingMgmtCont import FittingMgmtContInWnd, FittingMgmtContInStandalone 32 from eve.client.script.ui.shared.fitting.fittingUtil import GetDeletableNodes, DeleteFittings 33 from eve.common.script.sys.eveCfg import IsDocked 34 from eveexceptions import ServiceNotFound, UserError 35 from globalConfig.getFunctions import GetMaxShipsToFit 36 from inventorycommon.const import numVisibleSubsystems 37 from inventorycommon.util import IsModularShip, IsSubsystemFlag 38 from localization import GetByLabel 39 from menu import MenuLabel 40 MENU_ICON_NO = 'ui_73_16_50' 41 42 class FittingMgmt(Window): 43 __guid__ = 'form.FittingMgmt' 44 __notifyevents__ = ['OnRedrawFittingMgmt'] 45 default_windowID = 'FittingMgmt' 46 default_captionLabelPath = 'UI/Fitting/FittingWindow/FittingManagement/WindowCaption' 47 default_iconNum = 'res:/ui/Texture/WindowIcons/fittingManagement.png' 48 default_minSize = (525, 400) 49 default_width = 620 50 default_height = 600 51 _left_cont = None 52 _word_filter = None 53 _export_button = None 54 _fitting_management_cont = None 55 _search_field = None 56 _scroll = None 57 _left_underlay = None 58 59 def __init__(self, **kwargs): 60 self._selected_owner_id = session.charid 61 super(FittingMgmt, self).__init__(**kwargs) 62 63 def ApplyAttributes(self, attributes): 64 super(FittingMgmt, self).ApplyAttributes(attributes) 65 self.DrawLeftSide() 66 self.DrawRightSide() 67 self.HideRightPanel() 68 self.on_content_padding_changed.connect(self._on_window_content_padding_changed) 69 70 @property 71 def _fitting_service(self): 72 return sm.GetService('fittingSvc') 73 74 def DrawLeftSide(self): 75 self._left_cont = Container(name='leftside', parent=self.content, align=uiconst.TOLEFT, width=256) 76 self._left_underlay = PanelUnderlay(bgParent=self._left_cont, align=uiconst.TOALL, padding=self._get_left_underlay_padding()) 77 options = [ComboEntryData(label=GetByLabel('UI/Fitting/FittingWindow/FittingManagement/PersonalFittings'), returnValue=session.charid), ComboEntryData(label=GetByLabel('UI/Fitting/FittingWindow/FittingManagement/CorporationFittings'), returnValue=session.corpid)] 78 if session.allianceid: 79 options.append(ComboEntryData(label=GetByLabel('UI/Fitting/FittingWindow/FittingManagement/AllianceFittings'), returnValue=session.allianceid)) 80 selected = settings.user.ui.Get('savedFittingsCombo', None) 81 if selected not in [ x.returnValue for x in options ]: 82 selected = session.charid 83 self._selected_owner_id = selected 84 Combo(name='savedFittingsCombo', parent=self._left_cont, align=uiconst.TOTOP, padding=(0, 8, 8, 0), options=options, select=selected, callback=self.ChangeOwnerFilter) 85 search_cont = ContainerAutoSize(name='searchContainer', parent=self._left_cont, align=uiconst.TOTOP, alignMode=uiconst.TOTOP, top=8, padRight=8) 86 MenuButtonIcon(parent=ContainerAutoSize(parent=search_cont, align=uiconst.TOLEFT, left=-4), align=uiconst.CENTER, widht=24, height=24, texturePath=eveicon.load, iconSize=16, hint=GetByLabel('UI/Commands/Import'), get_menu_func=self._get_import_menu) 87 ButtonIcon(parent=ContainerAutoSize(parent=search_cont, align=uiconst.TOLEFT), align=uiconst.CENTER, widht=24, height=24, texturePath=eveicon.export, iconSize=16, hint=GetByLabel('UI/Commands/Export'), func=self.ExportFittings) 88 ButtonIcon(name='collapse', parent=ContainerAutoSize(parent=search_cont, align=uiconst.TOLEFT, padRight=8), align=uiconst.CENTER, width=24, height=24, iconSize=12, texturePath='res:/UI/Texture/classes/Scroll/Collapse.png', func=self.CollapseAll, hint=GetByLabel('UI/Common/Buttons/CollapseAll')) 89 self._search_field = SingleLineEditText(name='searchTextField', parent=search_cont, align=uiconst.TOTOP, maxLength=40, OnChange=self.Search, OnReturn=self.Search, hintText=GetByLabel('UI/Common/Buttons/Search')) 90 self._search_field.ShowClearButton() 91 self._scroll = eveScroll.Scroll(parent=self._left_cont, align=uiconst.TOALL, padding=(0, 8, 0, 0)) 92 self.DrawFittings() 93 divider = Divider(name='divider', parent=self.content, align=uiconst.TOLEFT, width=8, padRight=8, state=uiconst.UI_NORMAL, cross_axis_alignment=AxisAlignment.START) 94 divider.Startup(self._left_cont, 'width', 'x', 160, 350) 95 96 def _get_left_underlay_padding(self): 97 pad_left, _, _, pad_bottom = self.content_padding 98 return (-pad_left, 99 0, 100 0, 101 -pad_bottom) 102 103 def _update_left_underlay_padding(self): 104 if self._left_underlay: 105 self._left_underlay.padding = self._get_left_underlay_padding() 106 107 def _on_window_content_padding_changed(self, window): 108 self._update_left_underlay_padding() 109 110 def _get_import_menu(self): 111 menu = MenuData() 112 menu.AddEntry(text=GetByLabel('UI/Fitting/FittingWindow/FittingManagement/ImportFromFile'), func=self.ImportFittings) 113 menu.AddEntry(text=GetByLabel('UI/Fitting/FittingWindow/FittingManagement/ImportFromClipboard'), func=lambda : sm.GetService('fittingSvc').ImportFittingFromClipboard(), hint=GetByLabel('UI/Fitting/FittingWindow/FittingManagement/ImportFromClipboardHint')) 114 return menu 115 116 def ConstructImportExportButtons(self): 117 fitButtons = FlowContainer(name='buttonParent', parent=self._left_cont, align=uiconst.TOBOTTOM, padding=6, autoHeight=True, centerContent=True, contentSpacing=uiconst.BUTTONGROUPMARGIN, idx=0) 118 self._export_button = Button(parent=fitButtons, label=GetByLabel('UI/Commands/Export'), func=self.ExportFittings, align=uiconst.NOALIGN) 119 Button(parent=fitButtons, label=GetByLabel('UI/Commands/Import'), func=self.ImportFittings, align=uiconst.NOALIGN) 120 importFromClipboardButton = Button(parent=fitButtons, align=uiconst.NOALIGN, label=GetByLabel('UI/Fitting/FittingWindow/FittingManagement/ImportFromClipboard'), func=lambda button: sm.GetService('fittingSvc').ImportFittingFromClipboard()) 121 importFromClipboardButton.hint = GetByLabel('UI/Fitting/FittingWindow/FittingManagement/ImportFromClipboardHint') 122 123 def DrawRightSide(self): 124 self.noContentHint = EveCaptionLarge(parent=self.content, align=uiconst.TOTOP, padding=(32, 32, 32, 0), color=TextColor.SECONDARY, text=GetByLabel('UI/Fitting/FittingWindow/FittingManagement/NoFittingSelected'), textAlign=TextAlign.CENTER) 125 self._fitting_management_cont = FittingMgmtContInWnd(parent=self.content, align=uiconst.TOALL) 126 127 def EnableExportButton(self, enable): 128 if enable: 129 self._export_button.SetLabel(GetByLabel('UI/Commands/Export')) 130 self._export_button.state = uiconst.UI_NORMAL 131 else: 132 self._export_button.SetLabel(GetByLabel('UI/Fitting/FittingWindow/FittingManagement/ExportDisabled')) 133 self._export_button.state = uiconst.UI_DISABLED 134 135 def Search(self, *args): 136 self._perform_search() 137 138 @uthread2.debounce(wait=0.3) 139 def _perform_search(self): 140 query = self._search_field.GetValue() 141 if self._word_filter != query: 142 self._word_filter = query 143 self.DrawFittings() 144 145 def CollapseAll(self, *args): 146 self._scroll.CollapseAll() 147 148 def ChangeOwnerFilter(self, combo, label, ownerID, *args): 149 if self._selected_owner_id != ownerID: 150 self._selected_owner_id = ownerID 151 settings.user.ui.Set('savedFittingsCombo', ownerID) 152 self.DrawFittings() 153 self.HideRightPanel() 154 155 def DrawFittings(self, *args): 156 scrolllist = [] 157 fittings = self._fitting_service.GetFittings(self._selected_owner_id).items() 158 fittingsByGroupID = {} 159 shipGroups = [] 160 if self._word_filter: 161 query = self._word_filter.lower() 162 for fittingID, fitting in fittings[:]: 163 if query in fitting.name.lower(): 164 pass 165 elif evetypes.Exists(fitting.shipTypeID) and query in evetypes.GetName(fitting.shipTypeID).lower(): 166 pass 167 else: 168 fittings.remove((fittingID, fitting)) 169 170 for fittingID, fitting in fittings: 171 shipTypeID = fitting.shipTypeID 172 if not evetypes.Exists(shipTypeID): 173 log.LogError('Ship in stored fittings does not exist, shipID=%s, fittingID=%s' % (shipTypeID, fittingID)) 174 continue 175 groupID = evetypes.GetGroupID(shipTypeID) 176 if groupID not in fittingsByGroupID: 177 fittingsByGroupID[groupID] = [] 178 fittingsByGroupID[groupID].append(fitting) 179 groupName = evetypes.GetGroupNameByGroup(groupID) 180 if (groupName, groupID) not in shipGroups: 181 shipGroups.append((groupName, groupID)) 182 183 shipGroups.sort() 184 if len(fittings) == 0 and self._word_filter: 185 pass 186 else: 187 if self._selected_owner_id == session.charid: 188 maxFittings = const.maxCharFittings 189 elif self._selected_owner_id == session.corpid: 190 maxFittings = const.maxCorpFittings 191 else: 192 maxFittings = None 193 if maxFittings is not None: 194 label = GetByLabel('UI/Fitting/FittingWindow/FittingManagement/FittingsListHeader', numFittings=len(fittings), maxFittings=maxFittings) 195 scrolllist.append(GetFromClass(Header, {'label': label, 196 'selectable': 0})) 197 for groupName, groupID in shipGroups: 198 scrolllist.append(GetFromClass(ListGroup, {'GetSubContent': self.GetShipGroupSubContent, 199 'label': groupName, 200 'fittings': fittingsByGroupID[groupID], 201 'groupItems': fittingsByGroupID[groupID], 202 'id': ('fittingMgmtScrollWndGroup', groupName), 203 'showicon': 'hide', 204 'state': 'locked', 205 'BlockOpenWindow': 1, 206 'selectable': 0})) 207 208 self._scroll.Load(contentList=scrolllist, noContentHint=GetByLabel('UI/Common/NothingFound')) 209 210 def GetShipGroupSubContent(self, nodedata, *args): 211 scrolllist = [] 212 fittingsByType = {} 213 shipTypes = [] 214 for fitting in nodedata.fittings: 215 shipTypeID = fitting.shipTypeID 216 if not evetypes.Exists(shipTypeID): 217 log.LogError('Ship in stored fittings does not exist, shipID=%s, fittingID=%s' % (shipTypeID, fitting.fittingID)) 218 continue 219 if shipTypeID not in fittingsByType: 220 fittingsByType[shipTypeID] = [] 221 fittingsByType[shipTypeID].append(fitting) 222 typeName = evetypes.GetName(shipTypeID) 223 if (typeName, shipTypeID) not in shipTypes: 224 shipTypes.append((typeName, shipTypeID)) 225 226 shipTypes.sort() 227 for typeName, typeID in shipTypes: 228 scrolllist.append(GetFromClass(ListGroup, {'GetSubContent': self.GetFittingSubContent, 229 'label': typeName, 230 'groupItems': fittingsByType[typeID], 231 'fittings': fittingsByType[typeID], 232 'id': ('fittingMgmtScrollWndType', typeName), 233 'sublevel': 1, 234 'showicon': 'hide', 235 'state': 'locked', 236 'selectable': 0})) 237 238 return scrolllist 239 240 def GetFittingSubContent(self, nodedata, *args): 241 scrolllist = [] 242 fittings = [] 243 for fitting in nodedata.fittings: 244 fittings.append((fitting.name, fitting)) 245 246 fittings.sort() 247 for fittingName, fitting in fittings: 248 scrolllist.append((fittingName.lower(), GetFromClass(FittingEntry, {'label': fittingName, 249 'fittingID': fitting.fittingID, 250 'fitting': fitting, 251 'ownerID': self._selected_owner_id, 252 'showinfo': 1, 253 'GetMenu': self.GetFittingMenu, 254 'showicon': 'hide', 255 'sublevel': 2, 256 'OnClick': self.ClickEntry, 257 'ignoreRightClick': 1}))) 258 259 scrolllist = SortListOfTuples(scrolllist) 260 return scrolllist 261 262 def GetFittingMenu(self, entry): 263 m = [] 264 if not self or self.destroyed: 265 return m 266 node = entry.sr.node 267 selectedNodes = node.scroll.GetSelectedNodes(node) 268 multiSelected = len(selectedNodes) > 1 269 fittingID = entry.sr.node.fittingID 270 ownerID = entry.sr.node.ownerID 271 maxShipsAllowed = GetMaxShipsToFit(sm.GetService('machoNet')) 272 m = [] 273 if not multiSelected: 274 if evetypes.GetCategoryID(entry.sr.node.fitting.shipTypeID) != const.categoryStructure: 275 m += [(GetByLabel('UI/Fitting/FittingWindow/FitFittingToActiveShip'), self._fitting_service.LoadFittingFromFittingID, [ownerID, fittingID])] 276 if maxShipsAllowed and IsDocked(): 277 m += [(GetByLabel('UI/Fitting/FittingWindow/FittingManagement/OpenMultifit'), self.DoBulkFit, [entry])] 278 if session.role & ROLE_WORLDMOD: 279 try: 280 fitting_spawner = sm.GetService('fittingspawner') 281 except ServiceNotFound: 282 pass 283 else: 284 m.append(None) 285 m.append(('DEV Hax This Together!', fitting_spawner.SpawnFitting, [ownerID, entry.sr.node.fitting])) 286 m.append(('Mass DEV Hax This Together!', fitting_spawner.MassSpawnFitting, [ownerID, entry.sr.node.fitting])) 287 288 m += [None] 289 deletable = GetDeletableNodes(selectedNodes) 290 if deletable: 291 m += [(MenuLabel('UI/Fitting/FittingWindow/FittingManagement/DeleteFitting'), self.DeleteFitting, [entry, deletable])] 292 return m 293 294 def DeleteFitting(self, entry, selectedNodes): 295 fittingID = entry.sr.node.fittingID 296 fittingDeleted = DeleteFittings(selectedNodes) 297 if not fittingDeleted: 298 return 299 loadedFitting = self.GetVisibleFitting() 300 if loadedFitting is not None and loadedFitting.fittingID == fittingID: 301 self.HideRightPanel() 302 303 def DoBulkFit(self, entry): 304 fitting = entry.sr.node.fitting 305 OpenOrLoadMultiFitWnd(fitting) 306 307 def ClickEntry(self, entry, *args): 308 if not self or self.destroyed: 309 return 310 self.ShowRightPanel() 311 fitting = self.GetVisibleFitting() 312 if fitting is not None and fitting.fittingID == entry.sr.node.fitting.fittingID: 313 return 314 fitting = entry.sr.node.fitting 315 self._fitting_management_cont.LoadNewFitting(fitting) 316 317 def Fit(self, *args): 318 visibleFitting = self.GetVisibleFitting() 319 if visibleFitting is None: 320 return 321 self._fitting_service.LoadFittingFromFittingIDAndGetBuyOptionOnFailure(self._selected_owner_id, visibleFitting.fittingID) 322 323 def HideRightPanel(self): 324 self.noContentHint.Show() 325 self._fitting_management_cont.HidePanel() 326 327 def ShowRightPanel(self): 328 self.noContentHint.Hide() 329 self._fitting_management_cont.ShowPanel() 330 331 def GetVisibleFitting(self): 332 if self._fitting_management_cont: 333 return self._fitting_management_cont.GetSelectedFitting() 334 335 def ExportFittings(self, *args): 336 ExportFittingsWindow.Open(ownerID=self._selected_owner_id) 337 338 def ImportFittings(self, *args): 339 ImportFittingsWindow.Open() 340 341 def OnRedrawFittingMgmt(self, *args): 342 self.DrawFittings() 343 344 345 class ViewFitting(Window): 346 __guid__ = 'form.ViewFitting' 347 __nonpersistvars__ = [] 348 __notifyevents__ = ['OnSessionChanged'] 349 default_width = 410 350 default_height = 580 351 default_captionLabelPath = 'UI/Fitting/ShipFitting' 352 default_minSize = (250, 300) 353 default_windowID = 'ViewFitting' 354 default_iconNum = 'res:/ui/Texture/WindowIcons/fittingManagement.png' 355 356 def ApplyAttributes(self, attributes): 357 Window.ApplyAttributes(self, attributes) 358 self.fitting = attributes.fitting 359 self.truncated = attributes.truncated 360 self._fitting_service = sm.GetService('fittingSvc') 361 self.ReconstructLayout() 362 sm.RegisterNotify(self) 363 364 def GetHeaderIconMenu(self, *args): 365 m = MenuData() 366 self._AddCustomMenuOptions(m) 367 return m 368 369 def GetMenuMoreOptions(self): 370 menuData = super(ViewFitting, self).GetMenuMoreOptions() 371 self._AddCustomMenuOptions(menuData) 372 return menuData 373 374 def _AddCustomMenuOptions(self, menu): 375 if IsDocked(): 376 fittingcopy = self.fitting.copy() 377 menu.AddEntry(GetByLabel('UI/Fitting/FittingWindow/FittingManagement/OpenMultifit'), lambda : OpenOrLoadMultiFitWnd(fittingcopy)) 378 menu.AddEntry(GetByLabel('UI/Fitting/FittingWindow/FittingManagement/ExportToClipboard'), self._fitting_management_cont.ExportFittingToClipboard, hint=GetByLabel('UI/Fitting/FittingWindow/FittingManagement/ExportToClipboardHint')) 379 380 def ClickDragIcon(self, *args): 381 subsystems = {} 382 if IsModularShip(self.fitting.shipTypeID): 383 for typeID, flag, qty in self.fitting.fitData: 384 if IsSubsystemFlag(flag): 385 subsystems[evetypes.GetGroupID(typeID)] = typeID 386 387 if len(subsystems) != numVisibleSubsystems: 388 raise UserError('NotEnoughSubSystemsNotify', {}) 389 sm.GetService('preview').PreviewType(self.fitting.shipTypeID, subsystems) 390 391 def ReloadWnd(self, windowID, fitting, truncated): 392 self.fitting = fitting 393 self.truncated = truncated 394 self.windowID = windowID 395 self.ReconstructLayout() 396 397 def ReconstructLayout(self): 398 self.content.Flush() 399 self.UpdateHeader() 400 self._fitting_management_cont = FittingMgmtContInStandalone(parent=self.content, saveCallback=self.CloseByUser) 401 self._fitting_management_cont.LoadNewFitting(self.fitting, self.truncated) 402 403 def UpdateHeader(self): 404 self.UpdateCaption() 405 406 def UpdateCaption(self): 407 self.caption = GetByLabel(self.default_captionLabelPath) + ': %s' % evetypes.GetName(self.fitting.shipTypeID) 408 409 def OnSessionChanged(self, isRemote, session, change): 410 if 'stationid' in change or 'structureid' in change: 411 self.UpdateHeader() 412 413 414 def OpenOrLoadMultiFitWnd(fitting): 415 sm.GetService('fittingSvc').CheckBusyFittingAndRaiseIfNeeded() 416 from eve.client.script.ui.shared.fitting.multiFitWnd import MultiFitWnd 417 wnd = MultiFitWnd.GetIfOpen() 418 if wnd: 419 wnd.LoadWindow(fitting) 420 wnd.Maximize() 421 else: 422 wnd = MultiFitWnd.Open(fitting=fitting) 423 return wnd