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

invWindow.py (78376B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\shared\inventory\invWindow.py
      2 from math import pi
      3 import blue
      4 import eve.client.script.ui.shared.pointerTool.pointerToolConst as pConst
      5 import eveformat
      6 import eveicon
      7 import functools
      8 import inventorycommon.const as invConst
      9 import logging
     10 import telemetry
     11 import threadutils
     12 import weakref
     13 from carbon.common.script.sys.serviceConst import ROLE_GML, ROLE_WORLDMOD
     14 from carbonui import AxisAlignment, Density, TextColor, uiconst
     15 from carbonui.button.menu import MenuButtonIcon
     16 from carbonui.control.button import Button
     17 from carbonui.control.buttonIcon import ButtonIcon
     18 from carbonui.control.contextMenu.menuData import MenuData
     19 from carbonui.control.dragResizeCont import DragResizeCont
     20 from carbonui.control.scrollContainer import ScrollContainer
     21 from carbonui.control.window import Window
     22 from carbonui.decorative.panelUnderlay import PanelUnderlay
     23 from carbonui.primitives.container import Container
     24 from carbonui.primitives.containerAutoSize import ContainerAutoSize
     25 from carbonui.uianimations import animations
     26 from carbonui.uicore import uicore
     27 from carbonui.util.various_unsorted import IsUnder
     28 from carbonui.window.settings import GetRegisteredState
     29 from clientsettings.setting import UserSettingBool
     30 from eve.client.script.environment.invControllers import AssetSafetyDeliveries as AssetSafetyDeliveriesController, AssetSafetyContainer as AssetSafetyContainerController, GetInvCtrlFromInvID, ItemWreck, ItemFloatingCargo, LOOT_GROUPS, LOOT_GROUPS_NOCLOSE, ShipCargo as ShipCargoController, StationCorpDeliveries as StationCorpDeliveriesController, CapsuleerDeliveries as CapsuleerDeliveriesController, StationItems as StationItemsController, StationShips as StationShipsController, StructureItemHangar as StructureItemsController, StructureShipHangar as StructureShipsController
     31 from eve.client.script.parklife.states import flagWreckEmpty, mouseOver, selected
     32 from eve.client.script.ui import eveColor
     33 from eve.client.script.ui.control import eveLabel
     34 from eve.client.script.ui.control.eveLoadingWheel import LoadingWheel
     35 from eve.client.script.ui.control.historyBuffer import HistoryBuffer
     36 from eve.client.script.ui.inflight.bracketsAndTargets.bracketVarious import GetIconColor
     37 from eve.client.script.ui.plex.textures import PLEX_WINDOW_ICON
     38 from eve.client.script.ui.shared.container import InvContCapacityGauge, InvContQuickFilter
     39 from eve.client.script.ui.shared.inventory.invCommon import CONTAINERGROUPS
     40 from eve.client.script.ui.shared.inventory.invFilters import InvFilters
     41 from eve.client.script.ui.shared.inventory.invSettings import always_open_secondary_inventory_setting, keep_inv_quick_filter_setting, always_show_full_tree_setting, show_training_details
     42 from eve.client.script.ui.shared.inventory.treeData import TreeDataInv, TreeDataInvFolder, GetTreeDataClassByInvName
     43 from eve.client.script.ui.shared.inventory.treeViewEntries import GetTreeViewEntryClassByDataType
     44 from eve.common.lib import appConst
     45 from eve.common.lib import appConst as const
     46 from eve.common.script.sys.eveCfg import GetActiveShip, InShipInSpace, InStructure
     47 from eve.common.script.sys.idCheckers import IsStarbase
     48 from eve.common.script.util.eveFormat import FmtISKAndRound, GetAveragePrice
     49 from eveexceptions import UserError
     50 from localization import GetByLabel
     51 from omega.client.clone_grade_svc import get_clone_grade_svc
     52 from player_messaging.client.ui_message import prompt_player_fsd_dialog
     53 from signals import Signal
     54 from uthread2 import StartTasklet
     55 logger = logging.getLogger(__name__)
     56 HISTORY_LENGTH = 50
     57 TREE_DEFAULT_WIDTH = 160
     58 SORT_OPTIONS = [('UI/Common/Name', 'UI/Inventory/NameReversed', 'name'),
     59  ('UI/Inventory/ItemQuantity', 'UI/Inventory/QuantityReversed', 'qty'),
     60  ('UI/Common/Type', 'UI/Inventory/TypeReversed', 'type'),
     61  ('UI/Inventory/EstPrice', 'UI/Inventory/EstPriceReversed', 'estPrice')]
     62 
     63 class Inventory(Window):
     64     __guid__ = 'form.Inventory'
     65     __notifyevents__ = ['OnSessionChanged',
     66      'OnPrimaryViewChanged',
     67      'OnItemNameChange',
     68      'OnMultipleItemChange',
     69      'ProcessActiveShipChanged',
     70      'OnBeforeActiveShipChanged',
     71      'OnOfficeRentalChanged',
     72      'OnStateChange',
     73      'OnInvContDragEnter',
     74      'OnInvContDragExit',
     75      'DoBallsAdded',
     76      'DoBallRemove',
     77      'ProcessTempInvLocationAdded',
     78      'ProcessTempInvLocationRemoved',
     79      'OnSlimItemChange',
     80      'OnInvFiltersChanged',
     81      'OnInvContRefreshed',
     82      'OnCapacityChange',
     83      'OnWreckLootAll',
     84      'OnShowFullInvTreeChanged',
     85      'DoBallsRemove',
     86      'OnInvTreeUpdatingChanged']
     87     default_windowID = 'Inventory'
     88     default_width = 600
     89     default_height = 450
     90     default_minSize = (100, 140)
     91     default_iconNum = 'res:/UI/Texture/WindowIcons/items.png'
     92     default_isCompactable = True
     93     default_caption = None
     94     default_left = 80
     95     _filter_bottom_button = None
     96     _search_field = None
     97     _search_field_header_parent = None
     98     _search_field_top_parent = None
     99     _sort_by_button = None
    100     _top_action_container = None
    101     _view_mode_button = None
    102 
    103     def __init__(self, **kwargs):
    104         self.invController = None
    105         super(Inventory, self).__init__(**kwargs)
    106 
    107     @classmethod
    108     def Open(cls, invID = None, *args, **kwds):
    109         if invID:
    110             invController = GetInvCtrlFromInvID(invID)
    111             if invController and not invController.IsPrimed():
    112                 if not invController.IsInRange():
    113                     raise UserError('FakeItemNotFound')
    114                 try:
    115                     invController.GetItems()
    116                 except RuntimeError:
    117                     raise UserError('FakeItemNotFound')
    118 
    119         return super(Inventory, cls).Open(invID=invID, *args, **kwds)
    120 
    121     def ApplyAttributes(self, attributes):
    122         self.currInvID = attributes.Get('invID', None)
    123         self.rootInvID = attributes.Get('rootInvID', self.currInvID)
    124         self.invController = GetInvCtrlFromInvID(self.currInvID)
    125         super(Inventory, self).ApplyAttributes(attributes)
    126         sm.GetService('inv').Register(self)
    127         self.treeEntryByID = {}
    128         self.tempTreeEntryByID = {}
    129         self.dragHoverThread = None
    130         self.refreshTreeThread = None
    131         self.updateSelectedItemsThread = None
    132         self.updateSelectedItemsPending = None
    133         self.invCont = None
    134         self.loadingTreeView = False
    135         self.loadingInvCont = False
    136         self.dragOpenNewWindowCookie = None
    137         self.treeData = None
    138         self.treeDataTemp = None
    139         self.containersInRangeUpdater = None
    140         self.history = HistoryBuffer(HISTORY_LENGTH)
    141         self.breadcrumbInvIDs = []
    142         self.needsUpdate = True
    143         self._is_tree_expanded = True
    144         self._is_tree_expanded_setting = TreeExpandedSetting(self)
    145         self.onDividerMoved = Signal(signalName='onDividerMoved')
    146         if session.stationid:
    147             StationItemsController().GetItems()
    148         self.dividerCont = DragResizeCont(name='dividerCont', settingsID='invTreeViewWidth_%s' % self.GetWindowSettingsID(), parent=self.sr.main, align=uiconst.TOLEFT, minSize=45, maxSize=None, defaultSize=TREE_DEFAULT_WIDTH, onResizeCallback=self.OnDividerContResize, onDragCallback=self.OnDividerContDragged, handle_cross_axis_alignment=AxisAlignment.START)
    149         StartTasklet(self.OnDividerContResize)
    150         self._search_field_header_parent = ContainerAutoSize(parent=self.header.extra_content, align=uiconst.TORIGHT)
    151         treeCont = None
    152         treeCont = Container(parent=self.dividerCont.mainCont, align=uiconst.TOALL)
    153         self._tree_underlay = PanelUnderlay(parent=self.dividerCont.mainCont, align=uiconst.TOALL, padding=self._get_tree_underlay_padding())
    154         self.on_content_padding_changed.connect(self._update_tree_padding)
    155         self.treeBottomCont = InvFilters(parent=treeCont, settingsID='invFiltersHeight_%s' % self.GetWindowSettingsID(), minSize=100, maxSize=0.5, defaultSize=150, windowID=self.GetWindowSettingsID(), show_filter_toggle_button=False)
    156         self.treeBottomCont.on_filters_updated.connect(self.UpdateFilters)
    157         self.treeBottomCont.on_expand_collapse_animation.connect(self.ChangeTreeWhenAnimating)
    158         self.tree = ScrollContainer(name='tree', parent=treeCont, innerPadding=self._get_tree_inner_padding(), padLeft=-8)
    159         self.tree.GetMenu = self.GetMenu
    160         self.tree.Paste = self.Paste
    161         self.rightCont = Container(name='rightCont', parent=self.sr.main, padding=(-8, 0, 0, 0))
    162         self.noInventoryLabel = eveLabel.EveCaptionMedium(name='noInventoryLabel', parent=self.rightCont, state=uiconst.UI_HIDDEN, text=GetByLabel('UI/Inventory/NoInventoryLocationSelected'), pos=(17, 78, 0, 0), opacity=0.5)
    163         self.topRightCont2 = Container(name='topRightCont2', parent=self.rightCont, align=uiconst.TOTOP, height=24, padBottom=16, clipChildren=True)
    164         self.bottomRightCont = Container(name='bottomRightcont', parent=self.rightCont, align=uiconst.TOBOTTOM, height=24, padding=self._get_bottom_right_cont_padding(), clipChildren=True)
    165         self.specialActionsCont = ContainerAutoSize(name='specialActionsCont', parent=self.bottomRightCont, align=uiconst.TORIGHT, padding=(16, 0, 0, 0))
    166         self._top_action_container = ContainerAutoSize(parent=self.topRightCont2, align=uiconst.TOLEFT, padding=self._get_top_action_container_padding())
    167         ButtonIcon(parent=ContainerAutoSize(parent=self._top_action_container, align=uiconst.TOLEFT), align=uiconst.CENTER, width=24, height=24, iconSize=16, texturePath=eveicon.stack, hint=GetByLabel('UI/Inventory/StackAll'), func=self._stack_all, name=pConst.UNIQUE_NAME_INVENTORY_STACK_ALL, uniqueUiName=pConst.UNIQUE_NAME_INVENTORY_STACK_ALL)
    168         self._view_mode_button = MenuButtonIcon(parent=ContainerAutoSize(parent=self._top_action_container, align=uiconst.TOLEFT), align=uiconst.CENTER, width=24, height=24, iconSize=16, texturePath=eveicon.grid_view, get_menu_func=self._get_view_mode_menu, hint=self._get_view_mode_button_hint(), uniqueUiName=pConst.UNIQUE_NAME_INVENTORY_VIEW)
    169         self._sort_by_button = MenuButtonIcon(parent=ContainerAutoSize(parent=self._top_action_container, align=uiconst.TOLEFT), align=uiconst.CENTER, width=24, height=24, iconSize=16, texturePath=eveicon.bars_sort_ascending, get_menu_func=self._get_sort_menu, hint=GetByLabel('UI/Common/SortBy'))
    170         self._update_sort_by_button_display()
    171         dividerParent = Container(name='dividerParent', parent=self.topRightCont2, align=uiconst.TOALL, clipChildren=True)
    172         self.searchDividerCont = DragResizeCont(name='searchDividerCont', settingsID='searchDividerCont', parent=dividerParent, align=uiconst.TORIGHT, minSize=128, maxSize=0.9, favorMinSize=True, clipChildren=True, show_line=True)
    173         self._search_field_top_parent = Container(parent=self.searchDividerCont.mainCont, align=uiconst.TOALL)
    174         self.quickFilter = self._search_field = InvContQuickFilter(parent=self._get_search_field_parent(), align=self._get_search_field_align(), width=120)
    175         self._search_field.UpdateInputBoxAlign(self._get_search_field_align())
    176         self.capacityGauge = InvContCapacityGauge(parent=dividerParent, align=uiconst.TOALL)
    177         self.bottomRightLabelCont = Container(name='bottomRightLabelCont', parent=self.bottomRightCont, align=uiconst.TOALL, clipChildren=True)
    178         self.numItemsLabel = eveLabel.EveLabelMediumBold(name='numItemsLabel', parent=ContainerAutoSize(parent=self.bottomRightLabelCont, align=uiconst.TOLEFT), align=uiconst.CENTERLEFT, color=TextColor.SECONDARY)
    179         self.totalPriceLabel = eveLabel.EveLabelMedium(name='totalPriceLabel', parent=ContainerAutoSize(parent=self.bottomRightLabelCont, align=uiconst.TOLEFT, left=8), align=uiconst.CENTERLEFT, color=TextColor.SECONDARY)
    180         expanded = False
    181         if self._is_tree_expanded_setting.is_default_determined:
    182             expanded = self._is_tree_expanded_setting.get()
    183         if expanded:
    184             self._ExpandTree(animate=False)
    185         else:
    186             self._CollapseTree(animate=False)
    187         self._is_tree_expanded_setting.on_change.connect(self._on_tree_expanded_setting_changed)
    188         self.treeBottomCont.ExpandOrCollapeFilters()
    189         if not self.currInvID:
    190             self.currInvID = settings.user.ui.Get('invLastOpenContainerData_%s' % self.GetWindowSettingsID(), None)
    191         self.ShowInvContLoadingWheel()
    192         StartTasklet(self.ConstructFilters)
    193         shouldUpdateCorpHangarNodes = self._IsCorpHangarNode(self.currInvID)
    194         if self._IsCurrentInventoryValid():
    195             if shouldUpdateCorpHangarNodes:
    196                 self.currInvID = None
    197         else:
    198             self.currInvID = None
    199         StartTasklet(self.RefreshTree, corpHangarsChanged=shouldUpdateCorpHangarNodes)
    200         if self.compact:
    201             self.OnCompactModeEnabled()
    202         self.on_content_padding_changed.connect(self._on_content_padding_changed)
    203         self.on_compact_mode_changed.connect(self._on_compact_mode_changed)
    204 
    205     def _get_top_action_container_padding(self):
    206         if self.compact:
    207             return (0, 0, 0, 0)
    208         elif self._is_tree_expanded_setting.get():
    209             return (12, 0, 8, 0)
    210         else:
    211             return (-4, 0, 8, 0)
    212 
    213     def _update_top_action_container_padding(self, animate = True):
    214         if animate:
    215             for attr, value in zip(['padLeft',
    216              'padTop',
    217              'padRight',
    218              'padBottom'], self._get_top_action_container_padding()):
    219                 animations.MorphScalar(self._top_action_container, attr, endVal=value, duration=0.3)
    220 
    221         else:
    222             self._top_action_container.padding = self._get_top_action_container_padding()
    223 
    224     def _get_top_action_container_display(self):
    225         return not self.compact and self.invCont and self.invCont.IsViewModesEnabled()
    226 
    227     def _update_top_action_container_display(self):
    228         self._top_action_container.display = self._get_top_action_container_display()
    229 
    230     def _get_view_mode_menu(self):
    231         menu = MenuData()
    232         for viewMode in self.invCont.availableViewModes:
    233             menu.AddEntry(text=self.invCont.GetViewModeName(viewMode), texturePath=self.invCont.GetViewModeIcon(viewMode), func=functools.partial(self.SetInvContViewMode, viewMode))
    234 
    235         return menu
    236 
    237     def _get_sort_menu(self):
    238         active_criteria, active_direction = self._get_container_sort_by()
    239         menu = MenuData()
    240         for i, (label_ascending, label_descending, criteria) in enumerate(SORT_OPTIONS):
    241             if i > 0:
    242                 menu.AddSeparator()
    243             for direction, label in ((0, label_ascending), (1, label_descending)):
    244 
    245                 def select_sort_order(_criteria = criteria, _direction = direction):
    246                     self._sort_container_by(_criteria, _direction)
    247 
    248                 icon = eveicon.checkmark if active_criteria == criteria and active_direction == direction else None
    249                 menu.AddEntry(text=GetByLabel(label), texturePath=icon, func=select_sort_order)
    250 
    251         return menu
    252 
    253     def _get_container_sort_by(self):
    254         if self._has_sort_by():
    255             return (self.invCont.sortIconsBy, self.invCont.sortIconsDir)
    256         else:
    257             return (None, None)
    258 
    259     def _sort_container_by(self, criteria, direction):
    260         if self._has_sort_by():
    261             self.invCont.SortIconsBy(criteria, direction)
    262 
    263     def _update_sort_by_button_display(self):
    264         self._sort_by_button.display = self._has_sort_by()
    265 
    266     def _has_sort_by(self):
    267         return bool(self.invCont and self.invCont.ShouldShowSortBy())
    268 
    269     def _get_view_mode_icon(self):
    270         if self.invCont:
    271             return self.invCont.GetActiveViewModeIcon() or eveicon.eye
    272         return eveicon.eye
    273 
    274     def _get_view_mode_button_hint(self):
    275         if self.invCont:
    276             view_mode_name = self.invCont.GetActiveViewModeName()
    277             return GetByLabel('UI/Inventory/ViewModeWithCurrent', current_view_mode=view_mode_name)
    278 
    279     def _update_view_mode_button(self):
    280         icon = self._get_view_mode_icon()
    281         self._view_mode_button.SetTexturePath(icon)
    282         self._view_mode_button.hint = self._get_view_mode_button_hint()
    283 
    284     def _stack_all(self):
    285         if self.invCont:
    286             self.invCont.StackAll()
    287 
    288     def _get_search_field_parent(self):
    289         if self.compact:
    290             return self._search_field_header_parent
    291         else:
    292             return self._search_field_top_parent
    293 
    294     def _get_search_field_align(self):
    295         if self.compact:
    296             return uiconst.CENTER
    297         else:
    298             return uiconst.TOTOP
    299 
    300     def _update_search_field(self):
    301         parent = self._get_search_field_parent()
    302         align = self._get_search_field_align()
    303         if parent != self._search_field.parent:
    304             self._search_field.SetParent(None)
    305             self._search_field.align = align
    306             self._search_field.SetParent(parent)
    307         elif align != self._search_field.align:
    308             self._search_field.align = align
    309             self._search_field.UpdateInputBoxAlign(align)
    310 
    311     def _create_filter_bottom_button(self):
    312         if self._filter_bottom_button is None:
    313             self._filter_bottom_button = ButtonIcon(parent=Container(parent=self.bottomRightCont, align=uiconst.TOLEFT, idx=0), align=uiconst.TOPLEFT, width=24, height=24, iconSize=16, texturePath=eveicon.filter, hint=GetByLabel('UI/Inventory/FilterChipClear'), func=self._clear_all_filters)
    314 
    315     def _update_filter_bottom_button(self):
    316         if self.destroyed:
    317             return
    318         if self._is_filtered():
    319             if self._filter_bottom_button is None:
    320                 self._create_filter_bottom_button()
    321             animations.FadeIn(self._filter_bottom_button, duration=0.2)
    322             parent = self._filter_bottom_button.parent
    323             animations.MorphScalar(parent, 'width', startVal=parent.width, endVal=24, duration=0.2)
    324             animations.MorphScalar(parent, 'padRight', startVal=parent.padRight, endVal=4, duration=0.2)
    325         elif self._filter_bottom_button:
    326             animations.FadeOut(self._filter_bottom_button, duration=0.2)
    327             parent = self._filter_bottom_button.parent
    328             animations.MorphScalar(parent, 'width', startVal=parent.width, endVal=0, duration=0.2)
    329             animations.MorphScalar(parent, 'padRight', startVal=parent.padRight, endVal=0, duration=0.2)
    330 
    331     def _is_filtered(self):
    332         return self.invCont and (len(self.GetActiveFilters()) > 0 or self.quickFilter.GetQuickFilterInput())
    333 
    334     @threadutils.threaded
    335     def _on_tree_expanded_setting_changed(self, expanded):
    336         if expanded:
    337             self._ExpandTree()
    338         else:
    339             self._CollapseTree()
    340 
    341     def _get_tree_underlay_padding(self):
    342         pad_left, pad_top, _, pad_bottom = self.content_padding
    343         return (-pad_left,
    344          -pad_top,
    345          0,
    346          -pad_bottom)
    347 
    348     def _get_tree_inner_padding(self):
    349         _, content_pad_top, _, _ = self.content_padding
    350         pad_top = 0 if content_pad_top > 0 else 16
    351         return (0,
    352          pad_top,
    353          0,
    354          0)
    355 
    356     def _get_bottom_right_cont_padding(self):
    357         pad_top = self.content_padding[3]
    358         if self._is_tree_expanded:
    359             if self.compact:
    360                 pad_left = 8
    361             else:
    362                 pad_left = 16
    363         else:
    364             pad_left = 0
    365         return (pad_left,
    366          pad_top,
    367          0,
    368          0)
    369 
    370     def _on_content_padding_changed(self, window):
    371         self._update_bottom_right_cont_padding(animate=False)
    372 
    373     def _on_compact_mode_changed(self, window):
    374         self._update_bottom_right_cont_padding(animate=False)
    375 
    376     def _update_bottom_right_cont_padding(self, animate = True):
    377         if animate:
    378             for attr, value in zip(['padLeft',
    379              'padTop',
    380              'padRight',
    381              'padBottom'], self._get_bottom_right_cont_padding()):
    382                 animations.MorphScalar(obj=self.bottomRightCont, attrName=attr, startVal=getattr(self.bottomRightCont, attr), endVal=value, duration=0.2)
    383 
    384         else:
    385             self.bottomRightCont.padding = self._get_bottom_right_cont_padding()
    386 
    387     def _update_tree_padding(self, window):
    388         if self._tree_underlay is not None and not self._tree_underlay.destroyed:
    389             self._tree_underlay.padding = self._get_tree_underlay_padding()
    390         if self.tree is not None and not self.tree.destroyed:
    391             self.tree.innerPadding = self._get_tree_inner_padding()
    392 
    393     def _IsCurrentInventoryValid(self):
    394         if not session.structureid and not session.stationid:
    395             return True
    396         if self.currInvID:
    397             currInvCtrl = GetInvCtrlFromInvID(self.currInvID)
    398             if currInvCtrl:
    399                 try:
    400                     invItem = currInvCtrl.GetInventoryItem()
    401                     if invItem:
    402                         return sm.GetService('invCache').ValidateItemCanBeOpened(invItem)
    403                 except (KeyError, RuntimeError, UserError):
    404                     return False
    405 
    406         return True
    407 
    408     def GetMenuMoreOptions(self):
    409         menuData = super(Inventory, self).GetMenuMoreOptions()
    410         menuData.AddCheckbox(text=GetByLabel('UI/Inventory/ShowSidebar'), setting=self._is_tree_expanded_setting)
    411         menuData.AddCheckbox(text=GetByLabel('UI/Inventory/AlwaysOpenSeparate'), setting=always_open_secondary_inventory_setting)
    412         menuData.AddCheckbox(text=GetByLabel('UI/Inventory/KeepQuickFilterValue'), setting=keep_inv_quick_filter_setting)
    413         menuData.AddCheckbox(text=GetByLabel('UI/Inventory/AlwaysShowFullTree'), setting=always_show_full_tree_setting)
    414         if get_clone_grade_svc().is_omega_with_grace_period():
    415             menuData.AddCheckbox(text=GetByLabel('UI/Inventory/ShowTrainingDetails'), setting=show_training_details)
    416         if self.compact and self.invCont and self.invCont.IsViewModesEnabled():
    417             menuData.AddSeparator()
    418             menuData.AddCaption(text=GetByLabel('UI/Inventory/ViewMode'))
    419             for viewMode in self.invCont.availableViewModes:
    420                 menuData.AddRadioButton(text=self.invCont.GetViewModeName(viewMode), value=viewMode, setting=self.invCont.view_mode_setting)
    421 
    422         return menuData
    423 
    424     def OnSettingChangedSecondaryWnd(self, openSecondary, *args):
    425         settings.user.ui.Set('openSecondaryInv', openSecondary)
    426 
    427     def OnSettingChangedKeepQuickFilter(self, keepQuickFilterInput, *args):
    428         settings.user.ui.Set('keepInvQuickFilterInput', keepQuickFilterInput)
    429 
    430     def OnSettingChangedAlwaysShowFullTree(self, alwaysShowFullTree, *args):
    431         settings.user.ui.Set('alwaysShowFullInvTree', alwaysShowFullTree)
    432         sm.ScatterEvent('OnShowFullInvTreeChanged')
    433 
    434     def OnShowFullInvTreeChanged(self):
    435         self.RefreshTree()
    436 
    437     def GetRegisteredPositionAndSize(self):
    438         return self.GetRegisteredPositionAndSizeByClass(self.windowID)
    439 
    440     def RegisterPositionAndSize(self, key = None, windowID = None):
    441         windowID = self.windowID
    442         Window.RegisterPositionAndSize(self, key, windowID)
    443 
    444     def ShowTreeLoadingWheel(self):
    445         if self.loadingTreeView:
    446             return
    447         self.loadingTreeView = True
    448         StartTasklet(self._ShowTreeLoadingWheel)
    449 
    450     def _ShowTreeLoadingWheel(self):
    451         blue.synchro.SleepWallclock(500)
    452         wheelCont = Container(parent=self.dividerCont.mainCont)
    453         LoadingWheel(parent=wheelCont, align=uiconst.CENTER)
    454         while self.loadingTreeView:
    455             blue.synchro.Yield()
    456 
    457         wheelCont.Close()
    458 
    459     def HideTreeLoadingWheel(self):
    460         self.loadingTreeView = False
    461 
    462     def ShowInvContLoadingWheel(self):
    463         if self.loadingInvCont:
    464             return
    465         self.loadingInvCont = True
    466         StartTasklet(self._ShowInvContLoadingWheel)
    467 
    468     def _ShowInvContLoadingWheel(self):
    469         blue.synchro.SleepWallclock(500)
    470         wheel = LoadingWheel(parent=self.rightCont, align=uiconst.CENTER)
    471         while self.loadingInvCont:
    472             blue.synchro.Yield()
    473 
    474         wheel.Close()
    475 
    476     def HideInvContLoadingWheel(self):
    477         self.loadingInvCont = False
    478 
    479     def OnInvFiltersChanged(self):
    480         self.ConstructFilters()
    481         self.UpdateFilters()
    482 
    483     @telemetry.ZONE_METHOD
    484     def ConstructFilters(self):
    485         self.treeBottomCont.ConstructFilters()
    486 
    487     def RemoveTreeEntry(self, entry, byUser = False, checkRemoveParent = False):
    488         parent = entry.data.GetParent()
    489         if entry.childCont:
    490             for childEntry in entry.childCont.children[:]:
    491                 self.RemoveTreeEntry(childEntry)
    492 
    493         invID = entry.data.GetID()
    494         sm.GetService('inv').RemoveTemporaryInvLocation(invID, byUser)
    495         if invID == self.rootInvID:
    496             self.Close()
    497             return
    498         if invID in self.treeEntryByID:
    499             self.treeEntryByID.pop(invID)
    500         if invID in self.tempTreeEntryByID:
    501             self.tempTreeEntryByID.pop(invID)
    502         if entry.data in self.treeData.GetChildren():
    503             self.treeData.RemoveChild(entry.data)
    504         if invID == self.currInvID:
    505             if not self.IsInvTreeExpanded():
    506                 self.Close()
    507                 return
    508             self.ShowInvContainer(self.GetDefaultInvID())
    509         entry.Close()
    510         if checkRemoveParent and isinstance(parent, TreeDataInvFolder) and not parent.GetChildren():
    511             parEntry = self.treeEntryByID.get(parent.GetID(), None)
    512             if parEntry:
    513                 self.RemoveTreeEntry(parEntry, checkRemoveParent=True)
    514 
    515     def OnInvContScrollSelectionChanged(self, nodes):
    516         items = []
    517         for node in nodes:
    518             items.append(node.rec)
    519 
    520         self.UpdateSelectedItems(items)
    521 
    522     @telemetry.ZONE_METHOD
    523     def UpdateSelectedItems(self, items = None):
    524         if not session.IsItSafe():
    525             return
    526         if not self.invCont:
    527             return
    528         self.updateSelectedItemsPending = items or []
    529         if self.updateSelectedItemsThread:
    530             return
    531         self.updateSelectedItemsThread = StartTasklet(self._UpdateSelectedItems)
    532 
    533     @telemetry.ZONE_METHOD
    534     def _UpdateSelectedItems(self):
    535         try:
    536             while self.updateSelectedItemsPending is not None and not self.destroyed:
    537                 if session.mutating:
    538                     break
    539                 items = self.updateSelectedItemsPending
    540                 if not items and self.invCont:
    541                     iskItems = self.invCont.items
    542                     self.UpdateIskPriceLabel(iskItems)
    543                 else:
    544                     self.UpdateIskPriceLabel(items)
    545                 self.capacityGauge.SetSecondaryVolume(items)
    546                 self.capacityGauge.SetAdditionalVolume()
    547                 self.UpdateNumberOfItems(items)
    548                 self._update_filter_bottom_button()
    549                 self.updateSelectedItemsPending = None
    550                 blue.synchro.SleepWallclock(500)
    551 
    552         finally:
    553             self.updateSelectedItemsThread = None
    554 
    555     def SetInvContViewMode(self, value):
    556         if self.invCont:
    557             self.invCont.SetInvContViewMode(value)
    558         self.UpdateSelectedItems()
    559 
    560     @telemetry.ZONE_METHOD
    561     def UpdateNumberOfItems(self, items = None):
    562         items = items or []
    563         item_count_selected = len(items)
    564         item_count_filtered = self.invCont.numFilteredItems
    565         item_count_total = len(self.invCont.invController.GetItems())
    566         item_count_remaining = item_count_total - item_count_filtered
    567         if item_count_filtered > 0:
    568             filtered_text = GetByLabel('UI/Inventory/NumFiltered', numFiltered=item_count_filtered)
    569         else:
    570             filtered_text = ''
    571         if item_count_selected > 0:
    572             text = GetByLabel('UI/Inventory/NumItemsAndSelected2', numItems=item_count_remaining, numSelected=item_count_selected, numFilteredTxt=filtered_text)
    573         else:
    574             text = GetByLabel('UI/Inventory/NumItems2', numItems=item_count_remaining, numFilteredTxt=filtered_text)
    575         if item_count_filtered > 0:
    576             text = eveformat.color(text, eveColor.SUCCESS_GREEN)
    577         self.numItemsLabel.text = text
    578 
    579     def OnInvContDragEnter(self, invID, nodes):
    580         if not session.IsItSafe():
    581             return
    582         if invID != self.currInvID or self.invCont is None:
    583             return
    584         items = []
    585         for node in nodes:
    586             if getattr(node, 'item', None):
    587                 if self.invController.IsItemHereVolume(node.item):
    588                     return
    589                 items.append(node.item)
    590 
    591         self.capacityGauge.SetAdditionalVolume(items)
    592 
    593     def OnInvContDragExit(self, invID, nodes):
    594         if not session.IsItSafe():
    595             return
    596         self.capacityGauge.SetAdditionalVolume()
    597 
    598     @telemetry.ZONE_METHOD
    599     def UpdateIskPriceLabel(self, items):
    600         total = 0
    601         for item in items:
    602             if item is None:
    603                 continue
    604             price = GetAveragePrice(item)
    605             if price:
    606                 total += price * item.stacksize
    607 
    608         self.totalPriceLabel.text = GetByLabel('UI/Inventory/EstIskPrice2', iskString=FmtISKAndRound(total, False))
    609 
    610     def UpdateSpecialActionButtons(self):
    611         self.specialActionsCont.Flush()
    612         actions = self.invCont.invController.GetSpecialActions()
    613         for label, func, name, isDefaultBtn in actions:
    614             button = Button(parent=self.specialActionsCont, label=label, func=func, align=uiconst.CENTERLEFT, name=name, btn_default=isDefaultBtn, density=Density.COMPACT)
    615             self.invCont.RegisterSpecialActionButton(button)
    616 
    617         self.specialActionsCont.display = len(self.specialActionsCont.children) > 0
    618 
    619     def RegisterID(self, entry, invID):
    620         if id in self.treeEntryByID:
    621             raise ValueError('Duplicate inventory location ids: %s' % repr(invID))
    622         self.treeEntryByID[invID] = entry
    623 
    624     def UnregisterID(self, invID):
    625         if id in self.treeEntryByID:
    626             self.treeEntryByID.pop(invID)
    627 
    628     def OnTreeViewClick(self, entry, *args):
    629         if session.solarsystemid and hasattr(entry.data, 'GetItemID'):
    630             itemID = entry.data.GetItemID()
    631             isCurrStation = itemID in (session.stationid, session.structureid)
    632             bp = sm.GetService('michelle').GetBallpark()
    633             if bp and itemID in bp.slimItems and not isCurrStation:
    634                 sm.GetService('stateSvc').SetState(itemID, selected, 1)
    635         if hasattr(entry.data, 'OpenNewWindow') and uicore.uilib.Key(uiconst.VK_SHIFT) and entry.canAccess:
    636             entry.data.OpenNewWindow()
    637         elif isinstance(entry.data, TreeDataInv) and entry.data.HasInvCont():
    638             self.ShowInvContainer(entry.data.GetID())
    639         elif entry.data.HasChildren():
    640             entry.ToggleChildren()
    641 
    642     def OnTreeViewDblClick(self, entry, *args):
    643         if isinstance(entry.data, TreeDataInv) and entry.data.HasInvCont():
    644             if always_open_secondary_inventory_setting.is_enabled() and entry.canAccess:
    645                 entry.data.OpenNewWindow()
    646             else:
    647                 entry.ToggleChildren()
    648 
    649     def OnTreeViewMouseEnter(self, entry, *args):
    650         if not session.solarsystemid:
    651             return
    652         if hasattr(entry.data, 'GetItemID'):
    653             sm.GetService('stateSvc').SetState(entry.data.GetItemID(), mouseOver, 1)
    654 
    655     def OnTreeViewMouseExit(self, entry, *args):
    656         if not session.solarsystemid:
    657             return
    658         if hasattr(entry.data, 'GetItemID'):
    659             sm.GetService('stateSvc').SetState(entry.data.GetItemID(), mouseOver, 0)
    660 
    661     def OnTreeViewDragEnter(self, entry, dragObj, nodes):
    662         self.dragHoverThread = StartTasklet(self._OnTreeViewDragEnter, entry, dragObj, nodes)
    663 
    664     def OnTreeViewDragExit(self, entry, dragObj, nodes):
    665         sm.ScatterEvent('OnInvContDragExit', dragObj, nodes)
    666         if self.dragHoverThread:
    667             self.dragHoverThread.kill()
    668             self.dragHoverThread = None
    669 
    670     def _OnTreeViewDragEnter(self, entry, dragObj, nodes):
    671         blue.synchro.SleepWallclock(1000)
    672         if uicore.uilib.mouseOver == entry and uicore.uilib.leftbtn:
    673             entry.ShowChildren()
    674 
    675     def OnTreeViewGetDragData(self, entry):
    676         self.dragOpenNewWindowCookie = uicore.uilib.RegisterForTriuiEvents(uiconst.UI_MOUSEMOVEDRAG, self.OnGlobalDragExit, entry)
    677 
    678     def OnGlobalDragExit(self, entry, *args):
    679         if not uicore.IsDragging():
    680             return False
    681         else:
    682             mo = uicore.uilib.mouseOver
    683             if IsUnder(mo, self) or mo == self:
    684                 return True
    685             if entry.canAccess and hasattr(entry.data, 'OpenNewWindow'):
    686                 entry.CancelDrag()
    687                 invID = entry.data.GetID()
    688                 windowID = '%s_%s' % (invID[0], entry.settingsID)
    689                 windowInstanceID = '%s_%s' % (invID[1], entry.settingsID)
    690                 wnd = uicore.registry.GetWindow(windowID, windowInstanceID)
    691                 if wnd and wnd.InStack():
    692                     wnd.GetStack().RemoveWnd(wnd, (0, 5), dragging=True)
    693                 elif wnd:
    694                     StartTasklet(wnd._OpenDraggingThread)
    695                 else:
    696                     StartTasklet(entry.data.OpenNewWindow, True)
    697             return False
    698 
    699     @telemetry.ZONE_METHOD
    700     def ShowInvContainer(self, invID, branchHistory = True):
    701         if invID and not self.IsInvIDLegit(invID):
    702             invID = self.GetDefaultInvID(startFromInvID=invID)
    703             if invID not in self.treeEntryByID:
    704                 invID = None
    705         if invID is None:
    706             if self.invCont:
    707                 self.invCont.Close()
    708                 self.invCont = None
    709             self.noInventoryLabel.Show()
    710             self.HideInvContLoadingWheel()
    711             self._ExpandTree(animate=False)
    712             return
    713         self.noInventoryLabel.Hide()
    714         if self.invCont is not None and invID == self.invCont.invController.GetInvID():
    715             return
    716         entry = self.treeEntryByID.get(invID, None)
    717         if entry is None:
    718             return
    719         try:
    720             entry.data.invController.GetItems()
    721         except UserError:
    722             self.HideInvContLoadingWheel()
    723             if not self.invCont:
    724                 defaultInvID = self.GetDefaultInvID()
    725                 if defaultInvID != invID:
    726                     self.ShowInvContainer(defaultInvID)
    727             raise
    728 
    729         if self.invCont:
    730             self.invCont.Close()
    731         self.ShowInvContLoadingWheel()
    732         if keep_inv_quick_filter_setting.is_enabled():
    733             quickFilterInput = self.quickFilter.GetQuickFilterInput()
    734         else:
    735             quickFilterInput = None
    736         self.invCont = entry.data.GetInvCont(parent=self.rightCont, activeFilters=self.GetActiveFilters(), name=self.GetWindowSettingsID(), quickFilterInput=quickFilterInput, padding=(0 if not self._is_tree_expanded else (8 if self.compact else 16),
    737          0,
    738          0,
    739          0))
    740         self.invController = self.invCont.invController
    741         self.HideInvContLoadingWheel()
    742         self.invCont.view_mode_setting.on_change.connect(self._on_inv_cont_view_mode_changed)
    743         self.rightCont.state = uiconst.UI_NORMAL
    744         if hasattr(self.invCont, 'scroll') and self.invCont.scroll:
    745             self.rightCont.OnMouseDown = self.invCont.scroll.OnMouseDown
    746             self.rightCont.OnMouseUp = self.invCont.scroll.OnMouseUp
    747             self.invCont.GetRubberbandParentContainer = lambda : self.rightCont
    748             self.invCont.scroll.OnSelectionChange = self.OnInvContScrollSelectionChanged
    749         self.UpdateIskPriceLabel(self.invCont.invController.GetItems())
    750         self.UpdateSpecialActionButtons()
    751         self._update_view_mode_button()
    752         self._update_sort_by_button_display()
    753         self._update_filter_bottom_button()
    754         self.UpdateQuickFilter()
    755         self.UpdateCapacityGauge()
    756         self.UpdateTopRightCont()
    757         self.UpdateBottomRightCont()
    758         if branchHistory:
    759             self.history.Append(invID)
    760         previousInvID = self.currInvID
    761         self.currInvID = invID
    762         self.RegisterLastOpenInvID(invID)
    763         self.UpdateSelectedState()
    764         self.UpdateNumberOfItems()
    765         self.UpdateUIForCompactMode()
    766         sm.ScatterEvent('OnInventoryContainerShown', invID, previousInvID)
    767 
    768     def _on_inv_cont_view_mode_changed(self, view_mode):
    769         self._update_view_mode_button()
    770         self._update_sort_by_button_display()
    771 
    772     def UpdateQuickFilter(self):
    773         if self.invCont.IsQuickFilterEnabled():
    774             self.quickFilter.SetInvCont(self.invCont)
    775         if self.invCont.IsQuickFilterEnabled():
    776             self.quickFilter.Show()
    777         else:
    778             self.quickFilter.Hide()
    779 
    780     def UpdateCapacityGauge(self):
    781         if self.invCont.IsCapacityEnabled():
    782             self.capacityGauge.SetInvCont(self.invCont)
    783             self.capacityGauge.Show()
    784         else:
    785             self.capacityGauge.Hide()
    786 
    787     def UpdateTopRightCont(self):
    788         if self.invCont.IsCapacityEnabled() or self.invCont.IsQuickFilterEnabled() or self.invCont.IsViewModesEnabled():
    789             self.topRightCont2.Show()
    790         else:
    791             self.topRightCont2.Hide()
    792 
    793     def UpdateBottomRightCont(self):
    794         self.bottomRightCont.display = self.invCont is not None and self.invCont.IsEstimatedValueEnabled()
    795 
    796     def GetMenu(self):
    797         m = []
    798         if session.role & (ROLE_GML | ROLE_WORLDMOD):
    799             m.append(('GM / WM Extras', ('isDynamic', self.GetGMMenu, ())))
    800         m.extend(Window.GetMenu(self))
    801         return m
    802 
    803     def GetGMMenu(self):
    804         return [('Clear client inventory cache', sm.GetService('invCache').InvalidateCache, ()), ('Toggle inventory priming debug mode (Red means primed)', self.ToggleInventoryPrimingDebug, ()), ('Clear crystal dmg tracking', sm.GetService('inv').crystalDamageTracker.ClearDamageTrackingCache, ())]
    805 
    806     def ToggleInventoryPrimingDebug(self):
    807         isOn = settings.user.ui.Get('invPrimingDebugMode', False)
    808         settings.user.ui.Set('invPrimingDebugMode', not isOn)
    809 
    810     def OnBack(self):
    811         invID = self.history.GoBack()
    812         if invID:
    813             if invID in self.treeEntryByID:
    814                 self.ShowInvContainer(invID, branchHistory=False)
    815             else:
    816                 self.history.GoForward()
    817 
    818     def OnForward(self):
    819         invID = self.history.GoForward()
    820         if invID:
    821             if invID in self.treeEntryByID:
    822                 self.ShowInvContainer(invID, branchHistory=False)
    823             else:
    824                 self.history.GoBack()
    825 
    826     def Close(self, setClosed = False, *args, **kwds):
    827         super(Inventory, self).Close(setClosed, *args, **kwds)
    828         sm.ScatterEvent('OnInventoryClosed', self.currInvID)
    829 
    830     def OnResize_(self, *args):
    831         if self.InStack():
    832             width = self.GetStack().width
    833         else:
    834             width = self.width
    835         self.dividerCont.SetMaxSize(width - 10)
    836         self.treeBottomCont.UpdateSize()
    837         self.searchDividerCont.UpdateSize()
    838 
    839     def OnDividerContResize(self):
    840         minWidth, minHeight = self.default_minSize
    841         minWidth = max(self.dividerCont.width + 10, minWidth)
    842         minSize = (minWidth, minHeight)
    843         self.SetMinSize(minSize)
    844 
    845     def OnDividerContDragged(self):
    846         self.onDividerMoved()
    847 
    848     def RegisterLastOpenInvID(self, invID):
    849         settings.user.ui.Set('invLastOpenContainerData_%s' % self.GetWindowSettingsID(), invID)
    850 
    851     def DeselectAllFilters(self):
    852         self.treeBottomCont.DeselectAllFilters()
    853 
    854     def UpdateFilters(self):
    855         if self.invCont:
    856             self.SetActiveFilters(self.GetActiveFilters())
    857 
    858     def SetActiveFilters(self, filters):
    859         self.invCont.SetFilters(filters)
    860         self.treeBottomCont.SetActiveFilters(filters)
    861         self._update_filter_bottom_button()
    862 
    863     def OnInvContRefreshed(self, invCont):
    864         if self.invCont == invCont:
    865             self.UpdateSelectedItems()
    866 
    867     def GetActiveFilters(self):
    868         return self.treeBottomCont.GetActiveFilters()
    869 
    870     def ChangeTreeWhenAnimating(self, animationStart):
    871         if animationStart:
    872             self.tree.DisableScrollbars()
    873         else:
    874             self.tree.EnableScrollbars()
    875 
    876     def OnBreadcrumbLinkClicked(self, linkNum):
    877         invID = self.breadcrumbInvIDs[linkNum]
    878         if self.IsInvIDLegit(invID):
    879             self.ShowInvContainer(invID)
    880 
    881     def GetNeocomGroupIcon(self):
    882         return 'res:/UI/Texture/WindowIcons/folder_cargo.png'
    883 
    884     def GetNeocomGroupLabel(self):
    885         return GetByLabel('UI/Neocom/InventoryBtn')
    886 
    887     def GetDefaultWndIcon(self):
    888         if self.invController:
    889             return self.invController.GetIconName()
    890         return self.default_iconNum
    891 
    892     def GetWindowSettingsID(self):
    893         return self.windowID
    894 
    895     @staticmethod
    896     def GetWindowIDFromInvID(invID = None):
    897         invCtrlName = invID[0]
    898         if invID == (invConst.INVENTORY_ID_SHIP_CARGO, GetActiveShip()):
    899             return 'ActiveShipCargo'
    900         return invCtrlName
    901 
    902     @staticmethod
    903     def GetWindowIDPrimary():
    904         if session.stationid:
    905             return invConst.INVENTORY_ID_STATION
    906         elif session.structureid:
    907             return invConst.INVENTORY_ID_STRUCTURE
    908         else:
    909             return invConst.INVENTORY_ID_SPACE
    910 
    911     @staticmethod
    912     def OpenOrShow(invID = None, usePrimary = True, toggle = False, openFromWnd = None, windowID = None, windowInstanceID = None, **kw):
    913         if uicore.uilib.Key(uiconst.VK_SHIFT) or always_open_secondary_inventory_setting.is_enabled():
    914             usePrimary = False
    915             openFromWnd = None
    916         if not Inventory.IsPrimaryInvTreeExpanded():
    917             usePrimary = False
    918         if openFromWnd:
    919             if not isinstance(openFromWnd, Inventory):
    920                 openFromWnd = None
    921             else:
    922                 usePrimary = False
    923         if usePrimary or not invID:
    924             cls = InventoryPrimary
    925             if not windowID:
    926                 windowID = Inventory.GetWindowIDPrimary()
    927             rootInvID = None
    928         else:
    929             if invID == (invConst.INVENTORY_ID_SHIP_CARGO, GetActiveShip()):
    930                 cls = ActiveShipCargo
    931             else:
    932                 import form
    933                 cls = getattr(form, invID[0], Inventory)
    934             if not windowID:
    935                 windowID = Inventory.GetWindowIDFromInvID(invID)
    936             if not windowInstanceID:
    937                 windowInstanceID = invID[1]
    938             rootInvID = invID
    939         if toggle:
    940             wnd = cls.ToggleOpenClose(windowID=windowID, windowInstanceID=windowInstanceID, invID=invID, rootInvID=rootInvID, **kw)
    941         else:
    942             if openFromWnd:
    943                 wnd = openFromWnd
    944             else:
    945                 wnd = cls.GetIfOpen(windowID=windowID, windowInstanceID=windowInstanceID)
    946             if wnd:
    947                 wnd.Maximize()
    948                 if wnd.currInvID != invID:
    949                     if invID not in wnd.treeEntryByID:
    950                         wnd.RefreshTree(invID)
    951                     else:
    952                         wnd.ShowInvContainer(invID)
    953             else:
    954                 wnd = cls.Open(windowID=windowID, windowInstanceID=windowInstanceID, invID=invID, rootInvID=rootInvID, **kw)
    955         if wnd:
    956             wnd.ScrollToActiveEntry()
    957         return wnd
    958 
    959     def ScrollToActiveEntry(self):
    960         StartTasklet(self._ScrollToActiveEntry)
    961 
    962     def _ScrollToActiveEntry(self):
    963         blue.synchro.Yield()
    964         _, h = self.tree.GetAbsoluteSize()
    965         if h <= 0:
    966             return
    967         entry = self.treeEntryByID.get(self.currInvID, None)
    968         if not entry:
    969             return
    970         _, topEntry = entry.GetAbsolutePosition()
    971         _, topScroll, _, height = self.tree.mainCont.GetAbsolute()
    972         denum = height - entry.topRightCont.height
    973         if denum:
    974             fraction = float(topEntry - topScroll) / denum
    975             self.tree.ScrollToVertical(fraction)
    976 
    977     def OnDropData(self, dragObj, nodes):
    978         if self.invCont:
    979             return self.invCont.OnDropData(dragObj, nodes)
    980 
    981     def OnTreeViewDropData(self, entry, obj, nodes):
    982         if self.dragHoverThread:
    983             self.dragHoverThread.kill()
    984             self.dragHoverThread = None
    985         if self.dragOpenNewWindowCookie:
    986             uicore.uilib.UnregisterForTriuiEvents(self.dragOpenNewWindowCookie)
    987             self.dragOpenNewWindowCookie = None
    988         if isinstance(entry.data, TreeDataInv):
    989             sm.ScatterEvent('OnInvContDragExit', obj, nodes)
    990             StartTasklet(self._MoveItems, entry, nodes)
    991 
    992     def _MoveItems(self, entry, nodes):
    993         if not nodes:
    994             return
    995         if isinstance(nodes[0], TreeDataInv):
    996             item = nodes[0].invController.GetInventoryItem()
    997         else:
    998             item = getattr(nodes[0], 'item', None)
    999         if item and entry.data.invController.IsItemHere(item):
   1000             return
   1001         if isinstance(nodes[0], TreeDataInv) and not nodes[0].invController.IsMovable():
   1002             return
   1003         if entry.data.invController.OnDropData(nodes):
   1004             entry.Blink()
   1005 
   1006     def GetTreeEntryByItemID(self, itemID):
   1007         ret = []
   1008         for _, entry in self.treeEntryByID.iteritems():
   1009             if hasattr(entry.data, 'GetItemID') and entry.data.GetItemID() == itemID:
   1010                 ret.append(entry)
   1011 
   1012         return ret
   1013 
   1014     def GetTreeEntryByID(self, dataID):
   1015         ret = []
   1016         for _, entry in self.treeEntryByID.iteritems():
   1017             if hasattr(entry.data, 'GetID') and entry.data.GetID() == dataID:
   1018                 ret.append(entry)
   1019 
   1020         return ret
   1021 
   1022     def ProcessTempInvLocationAdded(self, invID):
   1023         if invID in self.treeEntryByID:
   1024             return
   1025         if self.rootInvID in sm.GetService('inv').GetTemporaryInvLocations():
   1026             return
   1027         invName, itemID = invID
   1028         cls = GetTreeDataClassByInvName(invName)
   1029         data = cls(invName, parent=self.treeDataTemp, itemID=itemID, isRemovable=True)
   1030         entry = self.CreateTreeViewEntry(data)
   1031         self.UpdateCelestialEntryStatus(entry)
   1032 
   1033     def ProcessTempInvLocationRemoved(self, invID, byUser):
   1034         if invID == self.currInvID and not byUser:
   1035             self.Close()
   1036         else:
   1037             entry = self.treeEntryByID.get(invID, None)
   1038             if entry:
   1039                 if self.treeDataTemp:
   1040                     self.treeDataTemp.RemoveChild(entry.data)
   1041                 if entry.data.IsRemovable():
   1042                     self.RemoveTreeEntry(entry)
   1043 
   1044     def OnSessionChanged(self, isRemote, sess, change):
   1045         if change.keys() == ['shipid']:
   1046             return
   1047         shouldUpdateCorpHangarNodes = False
   1048         if not self._IsCurrentInventoryValid():
   1049             shouldUpdateCorpHangarNodes = self._IsCorpHangarNode(self.currInvID)
   1050             self.currInvID = None
   1051         elif 'corprole' in change or 'corpid' in change:
   1052             shouldUpdateCorpHangarNodes = self._IsCorpHangarNode(self.currInvID)
   1053             if shouldUpdateCorpHangarNodes:
   1054                 self.currInvID = None
   1055         self.RefreshTree(corpHangarsChanged=shouldUpdateCorpHangarNodes)
   1056 
   1057     def _IsCorpHangarNode(self, invID):
   1058         if not invID or not (session.stationid or session.structureid):
   1059             return False
   1060         if not self.treeData:
   1061             return False
   1062         invData = self.treeData.GetChildByID(invID)
   1063         if not invData:
   1064             return False
   1065         office = sm.GetService('officeManager').GetCorpOfficeAtLocation()
   1066         if not office:
   1067             return False
   1068         invAncestorsData = invData.GetAncestors()
   1069         invAncestorsData.append(invData)
   1070         invAncestorsIDs = [ invData.GetID() for invData in invAncestorsData ]
   1071         invClass = 'StationCorpHangars' if session.stationid else 'StructureCorpHangar'
   1072         corpHangarItemID = office.officeID
   1073         corpHangarInvID = (invClass, corpHangarItemID)
   1074         return corpHangarInvID in invAncestorsIDs
   1075 
   1076     def OnPrimaryViewChanged(self, *args):
   1077         self.RefreshTree()
   1078 
   1079     def _IsInventoryItem(self, item):
   1080         if item.groupID in CONTAINERGROUPS:
   1081             return True
   1082         if item.categoryID == const.categoryShip:
   1083             return True
   1084         if item.typeID == const.typeAssetSafetyWrap:
   1085             return True
   1086         return False
   1087 
   1088     @telemetry.ZONE_METHOD
   1089     def OnMultipleItemChange(self, items, change):
   1090         self.UpdateSelectedItems()
   1091 
   1092     @telemetry.ZONE_METHOD
   1093     def OnInvChangeAny(self, item = None, change = None):
   1094         if not self._IsInventoryItem(item):
   1095             return
   1096         if item.itemID == GetActiveShip():
   1097             return
   1098         if item.categoryID == const.categoryShip and not session.stationid and not session.structureid:
   1099             return
   1100         if const.ixSingleton in change:
   1101             self.RefreshTree()
   1102             return
   1103         if not item.singleton:
   1104             return
   1105         if const.ixLocationID in change or const.ixFlag in change:
   1106             if session.stationid and item.categoryID == const.categoryShip:
   1107                 if session.charid in (item.ownerID, change.get(const.ixOwnerID, None)):
   1108                     self.RefreshTree()
   1109             elif session.solarsystemid and item.groupID in CONTAINERGROUPS and not InStructure():
   1110                 ownerIDs = (item.ownerID, change.get(const.ixOwnerID, None))
   1111                 if ownerIDs[0] == ownerIDs[1] == session.corpid:
   1112                     return
   1113                 if session.corpid in ownerIDs and session.charid not in ownerIDs:
   1114                     return
   1115                 self.RefreshTree()
   1116             else:
   1117                 shouldUpdateCorpHangarNodes = False
   1118                 if not self._IsCurrentInventoryValid():
   1119                     shouldUpdateCorpHangarNodes = self._IsCorpHangarNode(self.currInvID)
   1120                     self.currInvID = None
   1121                 self.RefreshTree(corpHangarsChanged=shouldUpdateCorpHangarNodes)
   1122         if const.ixOwnerID in change and item.typeID == const.typePlasticWrap:
   1123             self.RefreshTree()
   1124 
   1125     def GetSlimItem(self):
   1126         itemID = self.invController.itemID
   1127         bp = sm.GetService('michelle').GetBallpark()
   1128         if bp:
   1129             return bp.slimItems.get(itemID, None)
   1130 
   1131     @telemetry.ZONE_METHOD
   1132     def RemoveItem(self, item):
   1133         if session.solarsystemid and not self.invController.GetItems():
   1134             slimItem = self.GetSlimItem()
   1135             if slimItem is not None and slimItem.groupID in LOOT_GROUPS:
   1136                 self.RemoveWreckEntryOrClose()
   1137 
   1138     def OnWreckLootAll(self, invID, items):
   1139         if invID == self.currInvID:
   1140             self.RemoveWreckEntryOrClose()
   1141         treeEntry = self.treeEntryByID.get((invConst.INVENTORY_ID_SHIP_CARGO, GetActiveShip()))
   1142         if treeEntry and items:
   1143             treeEntry.Blink()
   1144 
   1145     def RemoveWreckEntryOrClose(self):
   1146         if self.IsInvTreeExpanded():
   1147             entry = self.treeEntryByID.get(self.currInvID, None)
   1148             if entry:
   1149                 self.SwitchToOtherLootable(entry)
   1150                 if entry.data.IsRemovable():
   1151                     self.RemoveTreeEntry(entry, byUser=True)
   1152         else:
   1153             slimItem = self.GetSlimItem()
   1154             if slimItem is not None and slimItem.groupID not in LOOT_GROUPS_NOCLOSE:
   1155                 self.CloseByUser()
   1156 
   1157     def SwitchToOtherLootable(self, oldEntry):
   1158         lootableData = [ data for data in self.treeDataTemp.GetChildren() if data.GetID()[0] in ('ItemWreck', 'ItemFloatingCargo') ]
   1159         if oldEntry.data not in lootableData:
   1160             return
   1161         idx = lootableData.index(oldEntry.data)
   1162         lootableData.remove(oldEntry.data)
   1163         if lootableData:
   1164             newIdx = min(len(lootableData) - 1, idx)
   1165             invID = lootableData[newIdx].GetID()
   1166             self.ShowInvContainer(invID)
   1167 
   1168     def OnStateChange(self, itemID, flag, isSet, *args):
   1169         if flag == flagWreckEmpty:
   1170             entries = self.GetTreeEntryByItemID(itemID)
   1171             for entry in entries:
   1172                 self.RemoveTreeEntry(entry)
   1173 
   1174     def OnSlimItemChange(self, oldSlim, newSlim):
   1175         if IsStarbase(newSlim.categoryID):
   1176             if oldSlim.posState != newSlim.posState:
   1177                 self.RefreshTree()
   1178 
   1179     def OnCapacityChange(self, itemID):
   1180         if self.invController and itemID == self.invController.itemID:
   1181             self.UpdateSelectedItems()
   1182             self.capacityGauge.RefreshCapacity()
   1183 
   1184     def DoBallsAdded(self, data):
   1185         for _, slimItem in data:
   1186             if slimItem.categoryID == const.categoryStarbase:
   1187                 self.RefreshTree()
   1188                 return
   1189 
   1190     @telemetry.ZONE_METHOD
   1191     def DoBallsRemove(self, pythonBalls, isRelease):
   1192         for ball, slimItem, terminal in pythonBalls:
   1193             self.DoBallRemove(ball, slimItem, terminal)
   1194 
   1195     def DoBallRemove(self, ball, slimItem, terminal):
   1196         StartTasklet(self._DoBallRemove, ball, slimItem, terminal)
   1197 
   1198     def _DoBallRemove(self, ball, slimItem, terminal):
   1199         if self and self.destroyed:
   1200             return
   1201         invID = (invConst.INVENTORY_ID_SHIP_CARGO, GetActiveShip())
   1202         if slimItem.itemID in (session.structureid, session.stationid):
   1203             return
   1204         for entry in self.GetTreeEntryByItemID(slimItem.itemID):
   1205             if entry.data.GetID() == invID:
   1206                 continue
   1207             if entry.data.IsDescendantOf(invID):
   1208                 continue
   1209             self.RemoveTreeEntry(entry, checkRemoveParent=True)
   1210 
   1211     def OnItemNameChange(self, *args):
   1212         self.RefreshTree()
   1213 
   1214     def ProcessActiveShipChanged(self, shipID, oldShipID):
   1215         self.RefreshTree()
   1216 
   1217     def OnCompactModeEnabled(self):
   1218         super(Inventory, self).OnCompactModeEnabled()
   1219         self.UpdateUIForCompactMode()
   1220         self.UpdateBottomRightCont()
   1221 
   1222     def OnCompactModeDisabled(self):
   1223         super(Inventory, self).OnCompactModeDisabled()
   1224         self.UpdateUIForCompactMode()
   1225         self.UpdateBottomRightCont()
   1226 
   1227     def UpdateUIForCompactMode(self):
   1228         if self.invController is None:
   1229             return
   1230         self.invController.SetCompactMode(self.IsCompact())
   1231         self._update_search_field()
   1232         self._update_top_action_container_padding(animate=False)
   1233         self._update_top_action_container_display()
   1234         if self.IsCompact():
   1235             self.searchDividerCont.Hide()
   1236             show_capacity = self.invCont and self.invCont.IsCapacityEnabled()
   1237             if show_capacity:
   1238                 self.topRightCont2.Show()
   1239                 self.capacityGauge.padding = 0
   1240                 self.capacityGauge.HideLabel()
   1241                 self.topRightCont2.height = 5
   1242             else:
   1243                 self.topRightCont2.Hide()
   1244             self.capacityGauge.state = uiconst.UI_NORMAL
   1245             self.rightCont.padLeft = -8 if self._is_tree_expanded else 0
   1246             if self.invCont:
   1247                 self.invCont.padLeft = 8 if self._is_tree_expanded else 0
   1248             self.topRightCont2.padLeft = 8 if self._is_tree_expanded else 0
   1249             self.topRightCont2.padBottom = 8
   1250         else:
   1251             self.searchDividerCont.Show()
   1252             if self.invCont and self.invCont.IsCapacityEnabled():
   1253                 self.topRightCont2.Show()
   1254                 self.capacityGauge.padding = (0, 0, 0, 0)
   1255                 self.capacityGauge.ShowLabel()
   1256                 self.topRightCont2.height = 24
   1257             if self.invCont:
   1258                 self.invCont.padLeft = 16 if self._is_tree_expanded else 0
   1259             self.capacityGauge.state = uiconst.UI_PICKCHILDREN
   1260             self.rightCont.padLeft = -8 if self._is_tree_expanded else 0
   1261             self.topRightCont2.padLeft = 0
   1262             self.topRightCont2.padBottom = 16
   1263 
   1264     def CollapseFilters(self, animate = True):
   1265         self.filterCont.Disable()
   1266         self.expandFiltersBtn.Disable()
   1267         self.expandFiltersBtn.SetRotation(pi)
   1268         self.treeBottomCont.DisableDragResize()
   1269         height = self.filterHeaderCont.height + 6
   1270         self.treeBottomCont.minSize = self.treeBottomCont.maxSize = height
   1271         self.filterCont.DisableScrollbars()
   1272         if animate:
   1273             self.tree.DisableScrollbars()
   1274             animations.MorphScalar(self.treeBottomCont, 'height', self.treeBottomCont.height, height, duration=0.3)
   1275             animations.FadeOut(self.filterCont, duration=0.3, sleep=True)
   1276             self.tree.EnableScrollbars()
   1277         self.treeBottomCont.height = height
   1278         self.filterCont.opacity = 0.0
   1279         self.expandFiltersBtn.Enable()
   1280         settings.user.ui.Set('invFiltersExpanded_%s' % self.GetWindowSettingsID(), False)
   1281 
   1282     def CollapseFilters(self, animate = True):
   1283         self.filterCont.Disable()
   1284         self.expandFiltersBtn.Disable()
   1285         self.expandFiltersBtn.SetRotation(pi)
   1286         self.treeBottomCont.DisableDragResize()
   1287         height = self.filterHeaderCont.height + 16
   1288         self.treeBottomCont.minSize = self.treeBottomCont.maxSize = height
   1289         self.filterCont.DisableScrollbars()
   1290         if animate:
   1291             self.tree.DisableScrollbars()
   1292             animations.MorphScalar(self.treeBottomCont, 'height', self.treeBottomCont.height, height, duration=0.3)
   1293             animations.FadeOut(self.filterCont, duration=0.3, sleep=True)
   1294             self.tree.EnableScrollbars()
   1295         self.treeBottomCont.height = height
   1296         self.filterCont.opacity = 0.0
   1297         self.expandFiltersBtn.Enable()
   1298         settings.user.ui.Set('invFiltersExpanded_%s' % self.GetWindowSettingsID(), False)
   1299 
   1300     def Paste(self, value):
   1301         if self.invCont:
   1302             self.invCont.Paste(value)
   1303 
   1304     def OnOfficeRentalChanged(self, corporationID, officeID):
   1305         if corporationID == session.corpid:
   1306             self.RefreshTree()
   1307 
   1308     @telemetry.ZONE_METHOD
   1309     def RefreshTree(self, invID = None, corpHangarsChanged = False):
   1310         if invID:
   1311             self.currInvID = invID
   1312         while not session.IsItSafe():
   1313             blue.pyos.synchro.SleepSim(250)
   1314 
   1315         if not sm.GetService('inv').IsTreeUpdatingEnabled() or self.refreshTreeThread and self.refreshTreeThread.tasklet.alive:
   1316             self.needsUpdate = True
   1317             return
   1318         if self.refreshTreeThread:
   1319             self.refreshTreeThread.kill()
   1320         self.refreshTreeThread = StartTasklet(self._RefreshTree, corpHangarsChanged)
   1321 
   1322     @telemetry.ZONE_METHOD
   1323     def _RefreshTree(self, corpHangarsChanged = False):
   1324         needsUpdate = True
   1325         while needsUpdate:
   1326             if self.destroyed:
   1327                 return
   1328             self.needsUpdate = False
   1329             if self.invCont:
   1330                 self.invCont.Disable()
   1331             self.tree.Disable()
   1332             try:
   1333                 self.ConstructTree(corpHangarsChanged)
   1334             finally:
   1335                 self.tree.Enable()
   1336                 if self.invCont:
   1337                     self.invCont.Enable()
   1338 
   1339             self.UpdateRangeUpdater()
   1340             needsUpdate = self.needsUpdate
   1341 
   1342     def IsInvIDLegit(self, invID):
   1343         data = self.treeData.GetDescendants().get(invID, None)
   1344         if data is None:
   1345             data = self.treeDataTemp.GetDescendants().get(invID, None)
   1346         if invID == self.treeData.GetID():
   1347             data = self.treeData
   1348         return data is not None and isinstance(data, TreeDataInv) and data.HasInvCont()
   1349 
   1350     def GetDefaultInvID(self, startFromInvID = None):
   1351         treeData = None
   1352         if startFromInvID:
   1353             treeData = self.treeData.GetChildByID(startFromInvID) or self.treeData
   1354         else:
   1355             treeData = self.treeData
   1356         invID = self._GetDefaultInvID([treeData])
   1357         if startFromInvID and invID is None:
   1358             return self.GetDefaultInvID()
   1359         else:
   1360             return invID
   1361 
   1362     def _IsValidDefaultInvID(self, data):
   1363         if isinstance(data, TreeDataInv) and data.HasInvCont():
   1364             if not data.IsValidDefaultSelection():
   1365                 return False
   1366             invController = GetInvCtrlFromInvID(data.GetID())
   1367             if invController.IsInRange():
   1368                 return True
   1369         return False
   1370 
   1371     def _GetDefaultInvID(self, dataNodes):
   1372         settingsInvID = settings.user.ui.Get('invLastOpenContainerData_%s' % self.GetWindowSettingsID(), None)
   1373         if settingsInvID:
   1374             for data in dataNodes:
   1375                 if data.GetID() == settingsInvID and self._IsValidDefaultInvID(data):
   1376                     return data.GetID()
   1377 
   1378         for data in dataNodes:
   1379             if self._IsValidDefaultInvID(data):
   1380                 return data.GetID()
   1381             if data.HasChildren():
   1382                 ret = self._GetDefaultInvID(data.GetChildren())
   1383                 if ret:
   1384                     return ret
   1385 
   1386     def ConstructTree(self, corpHangarsChanged = False):
   1387         self.treeEntryByID = {}
   1388         self.tree.Flush()
   1389         self.ShowTreeLoadingWheel()
   1390         try:
   1391             self.treeData = sm.GetService('inv').GetInvLocationTreeData(self.rootInvID)
   1392         except RuntimeError as e:
   1393             if e.args[0] == 'CharacterNotAtStation':
   1394                 return
   1395             raise
   1396 
   1397         self.treeDataTemp = sm.GetService('inv').GetInvLocationTreeDataTemp(self.rootInvID)
   1398         if not self.caption and self.rootInvID:
   1399             data = self.GetTreeDataByInvID(self.rootInvID)
   1400             if data:
   1401                 self.SetCaption(data.GetLabel())
   1402         if self.currInvID is None or not self.IsInvIDLegit(self.currInvID):
   1403             self.currInvID = self.GetDefaultInvID(self.currInvID)
   1404             if self.currInvID:
   1405                 GetInvCtrlFromInvID(self.currInvID).GetItems()
   1406                 self.treeData = sm.GetService('inv').GetInvLocationTreeData(self.rootInvID)
   1407         if self.rootInvID and not always_show_full_tree_setting.is_enabled():
   1408             tempData = self.treeDataTemp.GetChildByID(self.rootInvID)
   1409             rootNodes = []
   1410             if tempData:
   1411                 self.treeData = tempData
   1412                 rootNodes.append(self.treeData)
   1413             else:
   1414                 childData = self.treeData.GetChildByID(self.rootInvID)
   1415                 if childData:
   1416                     self.treeData = childData
   1417                 rootNodes.append(self.treeData)
   1418                 rootNodes.extend(self.treeDataTemp.GetChildren())
   1419         else:
   1420             rootNodes = self.treeData.GetChildren()
   1421             rootNodes.extend(self.treeDataTemp.GetChildren())
   1422         expanded = self._is_tree_expanded_setting.get()
   1423         if expanded:
   1424             self._ExpandTree(animate=False)
   1425         self.tree.opacity = 0.0
   1426         for data in rootNodes:
   1427             self.CreateTreeViewEntry(data)
   1428 
   1429         self.HideTreeLoadingWheel()
   1430         animations.FadeIn(self.tree, duration=0.2)
   1431         if self.currInvID:
   1432             self.UpdateSelectedState()
   1433             self.ScrollToActiveEntry()
   1434         if self.rootInvID is not None and self.rootInvID not in self.treeEntryByID:
   1435             self.Close()
   1436         else:
   1437             self.ShowInvContainer(self.currInvID)
   1438             if corpHangarsChanged:
   1439                 if self.rootInvID:
   1440                     self._CloseCorpHangars()
   1441                 else:
   1442                     self._CollapseCorpHangars()
   1443 
   1444     def CreateTreeViewEntry(self, data):
   1445         treeEntryClass = GetTreeViewEntryClassByDataType(data)
   1446         treeEntry = treeEntryClass(name=getattr(data, 'clsName', ''), parent=self.tree, level=0, eventListener=self, data=data, settingsID=self.GetWindowSettingsID(), onDividerMoved=self.onDividerMoved)
   1447         return treeEntry
   1448 
   1449     def _CloseCorpHangars(self):
   1450         if self._IsCorpHangarNode(self.rootInvID):
   1451             self.Close()
   1452 
   1453     def _CollapseCorpHangars(self):
   1454         corpHangarsNodeID = None
   1455         if session.structureid or session.stationid:
   1456             office = sm.GetService('officeManager').GetCorpOfficeAtLocation()
   1457             if office:
   1458                 corpHangarsNodeID = ('StationCorpHangars', office.officeID)
   1459         if not corpHangarsNodeID:
   1460             return
   1461         entries = self.GetTreeEntryByID(corpHangarsNodeID)
   1462         for entry in entries:
   1463             entry.HideChildren()
   1464 
   1465     def UpdateSelectedState(self):
   1466         selectedIDs = self.treeData.GetPathToDescendant(self.currInvID) or self.treeDataTemp.GetPathToDescendant(self.currInvID) or []
   1467         selectedIDs = [ node.GetID() for node in selectedIDs ]
   1468         if selectedIDs:
   1469             for entry in self.treeEntryByID.values():
   1470                 entry.UpdateSelectedState(selectedIDs=selectedIDs)
   1471 
   1472     def UpdateRangeUpdater(self):
   1473         if InShipInSpace() and not self.containersInRangeUpdater:
   1474             self.containersInRangeUpdater = StartTasklet(self.UpdateTreeViewEntriesInRange)
   1475         elif (session.structureid or session.stationid) and self.containersInRangeUpdater:
   1476             self.containersInRangeUpdater.kill()
   1477             self.containersInRangeUpdater = None
   1478 
   1479     def UpdateTreeViewEntriesInRange(self):
   1480         while not self.destroyed:
   1481             if session.solarsystemid is None:
   1482                 self.containersInRangeUpdater = None
   1483                 return
   1484             self._UpdateTreeViewEntriesInRange()
   1485 
   1486     def _UpdateTreeViewEntriesInRange(self):
   1487         for entry in self.treeEntryByID.values():
   1488             if not entry.display or entry.IsCompletelyClipped(self.tree):
   1489                 continue
   1490             self.UpdateCelestialEntryStatus(entry)
   1491             blue.pyos.BeNice()
   1492 
   1493         blue.synchro.Sleep(500)
   1494 
   1495     def UpdateCelestialEntryStatus(self, entry):
   1496         if hasattr(entry.data, 'GetLabelWithDistance'):
   1497             entry.label.text = entry.data.GetLabelWithDistance()
   1498         invController = getattr(entry.data, 'invController', None)
   1499         if invController is None:
   1500             canAccess = True
   1501         else:
   1502             canAccess = invController.IsInRange()
   1503             if isinstance(entry.data.invController, (ItemWreck, ItemFloatingCargo)):
   1504                 data = entry.data
   1505                 entry.icon.LoadIcon(data.GetIcon(), ignoreSize=True)
   1506                 slimItem = sm.GetService('michelle').GetBallpark().slimItems[data.invController.itemID]
   1507                 entry.iconColor = GetIconColor(slimItem)
   1508         entry.SetAccessability(canAccess)
   1509 
   1510     def OnExpandTreeBtn(self, *args):
   1511         if self.dividerCont.pickState:
   1512             self.CollapseTree()
   1513         else:
   1514             self.ExpandTree()
   1515         self.OnDividerContResize()
   1516 
   1517     def GetTreeDataByInvID(self, invID):
   1518         for root in (self.treeData, self.treeDataTemp):
   1519             data = root.GetChildByID(invID)
   1520             if data:
   1521                 return data
   1522 
   1523     def SetInvTreeExpandedSetting(self, isExpanded):
   1524         self._is_tree_expanded_setting.set(isExpanded)
   1525 
   1526     def IsInvTreeExpanded(self, getDefault = True):
   1527         return self._is_tree_expanded_setting.get()
   1528 
   1529     @staticmethod
   1530     def IsPrimaryInvTreeExpanded():
   1531         windowID = Inventory.GetWindowIDPrimary()
   1532         return bool(settings.user.ui.Get('invTreeExpanded_%s' % windowID, True))
   1533 
   1534     @staticmethod
   1535     def IsPrimaryInvCompacted():
   1536         windowID = Inventory.GetWindowIDPrimary()
   1537         return GetRegisteredState(windowID, 'compact')
   1538 
   1539     def GetDefaultInvTreeExpanded(self):
   1540         if not self.rootInvID:
   1541             return True
   1542         if not self.treeData:
   1543             return None
   1544         data = self.treeData.GetChildByID(self.rootInvID)
   1545         if data:
   1546             return data.HasChildren()
   1547 
   1548     def ExpandTree(self, animate = True):
   1549         self._ExpandTree(animate)
   1550         self.SetInvTreeExpandedSetting(True)
   1551 
   1552     def _ExpandTree(self, animate = True):
   1553         if self._is_tree_expanded:
   1554             return
   1555         self._is_tree_expanded = True
   1556         width = settings.user.ui.Get('invTreeViewWidth_%s' % self.GetWindowSettingsID(), TREE_DEFAULT_WIDTH)
   1557         self._update_top_action_container_padding(animate)
   1558         self._update_bottom_right_cont_padding(animate)
   1559         if animate:
   1560             DURATION = 0.3
   1561             animations.MorphScalar(self.dividerCont, 'width', self.dividerCont.width, width, duration=DURATION)
   1562             animations.MorphScalar(self.rightCont, 'padLeft', endVal=-8, duration=DURATION)
   1563             if self.invCont:
   1564                 animations.MorphScalar(self.invCont, 'padLeft', endVal=8 if self.compact else 16, duration=DURATION)
   1565             if self.compact:
   1566                 animations.MorphScalar(self.topRightCont2, 'padLeft', endVal=8, duration=DURATION)
   1567             animations.FadeIn(self.dividerCont, duration=0.3, sleep=True)
   1568         else:
   1569             self.dividerCont.width = width
   1570             self.rightCont.padLeft = -8
   1571             if self.invCont:
   1572                 self.invCont.padLeft = 8 if self.compact else 16
   1573             if self.compact:
   1574                 self.topRightCont2.padLeft = 8
   1575         self.dividerCont.state = uiconst.UI_PICKCHILDREN
   1576         self.OnDividerContResize()
   1577 
   1578     def CollapseTree(self, animate = True):
   1579         self._CollapseTree(animate)
   1580         self.SetInvTreeExpandedSetting(False)
   1581 
   1582     def _CollapseTree(self, animate = True):
   1583         if not self._is_tree_expanded:
   1584             return
   1585         self._is_tree_expanded = False
   1586         self.dividerCont.state = uiconst.UI_DISABLED
   1587         self._update_top_action_container_padding(animate)
   1588         self._update_bottom_right_cont_padding(animate)
   1589         if animate:
   1590             DURATION = 0.3
   1591             animations.MorphScalar(self.dividerCont, 'width', self.dividerCont.width, 0.0, duration=DURATION)
   1592             animations.MorphScalar(self.rightCont, 'padLeft', endVal=0, duration=DURATION)
   1593             if self.invCont:
   1594                 animations.MorphScalar(self.invCont, 'padLeft', endVal=0, duration=DURATION)
   1595             if self.compact:
   1596                 animations.MorphScalar(self.topRightCont2, 'padLeft', endVal=0, duration=DURATION)
   1597             animations.FadeOut(self.dividerCont, duration=DURATION, sleep=True)
   1598         else:
   1599             self.dividerCont.width = 0
   1600             self.rightCont.padLeft = 0
   1601             if self.invCont:
   1602                 self.invCont.padLeft = 0
   1603             if self.compact:
   1604                 self.topRightCont2.padLeft = 0
   1605         self.OnDividerContResize()
   1606 
   1607     def OnInvTreeUpdatingChanged(self, newState):
   1608         if newState and self.needsUpdate:
   1609             self.RefreshTree()
   1610 
   1611     def _clear_all_filters(self):
   1612         self.DeselectAllFilters()
   1613         if self.invCont:
   1614             self.quickFilter.ClearFilter()
   1615         self._update_filter_bottom_button()
   1616 
   1617 
   1618 class InventoryPrimary(Inventory):
   1619     __guid__ = 'form.InventoryPrimary'
   1620     default_windowID = 'InventoryPrimary'
   1621     default_caption = 'UI/Neocom/InventoryBtn'
   1622 
   1623     def GetDefaultWndIcon(self):
   1624         return self.default_iconNum
   1625 
   1626     def ProcessActiveShipChanged(self, shipID, oldShipID):
   1627         if self.currInvID == (invConst.INVENTORY_ID_SHIP_CARGO, oldShipID):
   1628             invID = (invConst.INVENTORY_ID_SHIP_CARGO, shipID)
   1629         else:
   1630             invID = None
   1631         self.RefreshTree(invID)
   1632 
   1633     def ApplyAttributes(self, attributes):
   1634         super(InventoryPrimary, self).ApplyAttributes(attributes)
   1635         if session.stationid:
   1636             self.SetScope(uiconst.SCOPE_STATION)
   1637         elif session.structureid:
   1638             self.SetScope(uiconst.SCOPE_STRUCTURE)
   1639         else:
   1640             self.SetScope(uiconst.SCOPE_INFLIGHT)
   1641 
   1642     @classmethod
   1643     def Open(cls, windowID = None, *args, **kwds):
   1644         if not windowID:
   1645             windowID = Inventory.GetWindowIDPrimary()
   1646         return super(InventoryPrimary, cls).Open(windowID=windowID, *args, **kwds)
   1647 
   1648     @classmethod
   1649     def GetIfOpen(cls, windowID = None, windowInstanceID = None):
   1650         if not windowID:
   1651             windowID = Inventory.GetWindowIDPrimary()
   1652         return super(InventoryPrimary, cls).GetIfOpen(windowID, windowInstanceID)
   1653 
   1654 
   1655 class InventorySecondary(Inventory):
   1656 
   1657     @classmethod
   1658     def Open(cls, invID = None, *args, **kwds):
   1659         if not invID:
   1660             invID = (kwds.get('windowID', cls.default_windowID), kwds.get('windowInstanceID', None))
   1661         return super(InventorySecondary, cls).Open(invID=invID, *args, **kwds)
   1662 
   1663 
   1664 class StationItems(InventorySecondary):
   1665     __guid__ = 'form.' + invConst.INVENTORY_ID_STATION_ITEMS
   1666     default_windowID = invConst.INVENTORY_ID_STATION_ITEMS
   1667     default_scope = uiconst.SCOPE_DOCKED
   1668     default_iconNum = StationItemsController.iconName
   1669 
   1670     @classmethod
   1671     def OnDropDataCls(cls, dragObj, nodes):
   1672         return StationItemsController().OnDropData(nodes)
   1673 
   1674 
   1675 class StructureItems(InventorySecondary):
   1676     __guid__ = 'form.' + invConst.INVENTORY_ID_STRUCTURE_ITEMS
   1677     default_windowID = invConst.INVENTORY_ID_STRUCTURE_ITEMS
   1678     default_scope = uiconst.SCOPE_DOCKED
   1679     default_iconNum = StructureItemsController.iconName
   1680 
   1681     @classmethod
   1682     def OnDropDataCls(cls, dragObj, nodes):
   1683         return StructureItemsController().OnDropData(nodes)
   1684 
   1685 
   1686 class PlexVaultWindow(InventorySecondary):
   1687     __guid__ = 'form.PlexVault'
   1688     default_windowID = invConst.INVENTORY_ID_PLEX_VAULT
   1689     default_iconNum = PLEX_WINDOW_ICON
   1690 
   1691 
   1692 class StationShips(InventorySecondary):
   1693     __guid__ = 'form.' + invConst.INVENTORY_ID_STATION_SHIPS
   1694     default_windowID = invConst.INVENTORY_ID_STATION_SHIPS
   1695     default_scope = uiconst.SCOPE_DOCKED
   1696     default_iconNum = StationShipsController.iconName
   1697 
   1698     @classmethod
   1699     def OnDropDataCls(cls, dragObj, nodes):
   1700         return StationShipsController().OnDropData(nodes)
   1701 
   1702     def ShowInvContainer(self, invID, *args, **kw):
   1703         if invID[1] == GetActiveShip():
   1704             InventorySecondary.OpenOrShow(invID, usePrimary=False)
   1705         else:
   1706             InventorySecondary.ShowInvContainer(self, invID, *args, **kw)
   1707 
   1708 
   1709 class StructureShips(StationShips):
   1710     __guid__ = 'form.' + invConst.INVENTORY_ID_STRUCTURE_SHIPS
   1711     default_windowID = invConst.INVENTORY_ID_STRUCTURE_SHIPS
   1712     default_scope = uiconst.SCOPE_DOCKED
   1713     default_iconNum = StructureShipsController.iconName
   1714 
   1715     @classmethod
   1716     def OnDropDataCls(cls, dragObj, nodes):
   1717         return StructureShipsController().OnDropData(nodes)
   1718 
   1719 
   1720 class StationCorpHangars(InventorySecondary):
   1721     __guid__ = 'form.StationCorpHangars'
   1722     default_windowID = invConst.INVENTORY_ID_STATION_CORP_HANGARS
   1723     default_scope = uiconst.SCOPE_DOCKED
   1724     default_iconNum = 'res:/ui/Texture/WindowIcons/corpHangar.png'
   1725 
   1726     def GetDefaultWndIcon(self):
   1727         return self.default_iconNum
   1728 
   1729 
   1730 class StationCorpDeliveries(InventorySecondary):
   1731     __guid__ = 'form.StationCorpDeliveries'
   1732     default_windowID = invConst.INVENTORY_ID_STATION_CORP_DELIVERIES
   1733     default_scope = uiconst.SCOPE_DOCKED
   1734     default_iconNum = StationCorpDeliveriesController.iconName
   1735 
   1736     @classmethod
   1737     def OnDropDataCls(cls, dragObj, nodes):
   1738         return StationCorpDeliveriesController().OnDropData(nodes)
   1739 
   1740     @classmethod
   1741     def Open(cls, invID = None, *args, **kwds):
   1742         deliveryRoles = appConst.corpRoleAccountant | appConst.corpRoleJuniorAccountant | appConst.corpRoleTrader
   1743         if session.corprole & deliveryRoles == 0:
   1744             prompt_player_fsd_dialog('CrpAccessDenied', {'reason': GetByLabel('UI/Commands/CorpRoleMissing')})
   1745             allOpen = settings.char.windows.Get('openWindows', {})
   1746             allOpen.pop('CorpMarketHangar', None)
   1747             settings.char.windows.Set('openWindows', allOpen)
   1748             return
   1749         return super(StationCorpDeliveries, cls).Open(invID, *args, **kwds)
   1750 
   1751 
   1752 class CapsuleerDeliveries(InventorySecondary):
   1753     __guid__ = 'form.CapsuleerDeliveries'
   1754     default_windowID = invConst.INVENTORY_ID_CORPORATION_ITEM_DELIVERIES
   1755     default_scope = uiconst.SCOPE_DOCKED
   1756     default_iconNum = CapsuleerDeliveriesController.iconName
   1757 
   1758     @classmethod
   1759     def OnDropDataCls(cls, _dragObj, nodes):
   1760         return CapsuleerDeliveriesController().OnDropData(nodes)
   1761 
   1762     @classmethod
   1763     def Open(cls, invID = None, *args, **kwds):
   1764         deliveryRoles = appConst.corpRoleProjectManager | appConst.corpRoleDirector
   1765         if session.corprole & deliveryRoles == 0:
   1766             prompt_player_fsd_dialog('CrpAccessDenied', {'reason': GetByLabel('UI/Commands/ProjectManagerOrDirectorRoleMissing')})
   1767             allOpen = settings.char.windows.Get('openWindows', {})
   1768             allOpen.pop('CapsuleerDeliveries', None)
   1769             settings.char.windows.Set('openWindows', allOpen)
   1770             return
   1771         return super(CapsuleerDeliveries, cls).Open(invID, *args, **kwds)
   1772 
   1773 
   1774 class AssetSafetyDeliveries(InventorySecondary):
   1775     __guid__ = 'form.AssetSafetyDeliveries'
   1776     default_windowID = 'AssetSafetyDeliveries'
   1777     default_scope = uiconst.SCOPE_DOCKED
   1778     default_iconNum = AssetSafetyDeliveriesController.iconName
   1779 
   1780     @classmethod
   1781     def OnDropDataCls(cls, dragObj, nodes):
   1782         return None
   1783 
   1784 
   1785 class AssetSafetyContainer(InventorySecondary):
   1786     __guid__ = 'form.AssetSafetyContainer'
   1787     default_windowID = 'AssetSafetyContainer'
   1788     default_scope = uiconst.SCOPE_DOCKED
   1789     default_iconNum = AssetSafetyContainerController.iconName
   1790 
   1791     @classmethod
   1792     def OnDropDataCls(cls, dragObj, nodes):
   1793         return None
   1794 
   1795 
   1796 class ActiveShipCargo(InventorySecondary):
   1797     __guid__ = 'form.ActiveShipCargo'
   1798     default_windowID = 'ActiveShipCargo'
   1799     default_iconNum = 'res:/UI/Texture/WindowIcons/cargo.png'
   1800     default_caption = 'UI/Neocom/ActiveShipCargoBtn'
   1801 
   1802     def ProcessActiveShipChanged(self, shipID, oldShipID):
   1803         self.rootInvID = (invConst.INVENTORY_ID_SHIP_CARGO, shipID)
   1804         self.RefreshTree()
   1805 
   1806     def GetDefaultWndIcon(self):
   1807         return self.default_iconNum
   1808 
   1809     @classmethod
   1810     def OnDropDataCls(cls, dragObj, nodes):
   1811         return ShipCargoController(GetActiveShip()).OnDropData(nodes)
   1812 
   1813 
   1814 class TreeExpandedSetting(UserSettingBool):
   1815 
   1816     def __init__(self, window):
   1817         self._window_ref = weakref.ref(window)
   1818         super(TreeExpandedSetting, self).__init__(settings_key='invTreeExpanded_{}'.format(window.windowID), default_value=self._get_default_value)
   1819 
   1820     @property
   1821     def is_default_determined(self):
   1822         window = self._window_ref()
   1823         if window is None:
   1824             return False
   1825         if not window.rootInvID:
   1826             return True
   1827         return bool(window.treeData)
   1828 
   1829     def set(self, value):
   1830         if self.is_default_determined:
   1831             super(TreeExpandedSetting, self).set(value)
   1832 
   1833     def _get_default_value(self):
   1834         window = self._window_ref()
   1835         if window is None:
   1836             return False
   1837         if not window.rootInvID:
   1838             return True
   1839         if not window.treeData:
   1840             return False
   1841         data = window.treeData.GetChildByID(window.rootInvID)
   1842         if data:
   1843             return data.HasChildren()
   1844         return False