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

container.py (60902B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\shared\container.py
      2 from math import pi, fabs
      3 import eveicon
      4 import evetypes
      5 import eveui
      6 import gametime
      7 import mathext
      8 from carbon.common.script.sys.serviceConst import ROLE_GML, ROLE_PROGRAMMER, ROLE_WORLDMOD
      9 from carbon.common.script.util import timerstuff
     10 from carbon.common.script.util.commonutils import StripTags
     11 from carbonui import Density
     12 from carbonui.control.contextMenu.menuData import MenuData
     13 from carbonui.control.scrollentries import SE_BaseClassCore
     14 from carbonui.control.singlelineedits.singleLineEditText import SingleLineEditText
     15 from carbonui.primitives.container import Container
     16 from carbonui.primitives.containerAutoSize import ContainerAutoSize
     17 from carbonui.primitives.fill import Fill
     18 from clientsettings.setting import UserSettingEnum
     19 from carbonui.util.various_unsorted import NiceFilter
     20 from eve.client.script.ui import eveColor, eveThemeColor
     21 from eve.client.script.ui.control import eveLabel, eveScroll
     22 from carbonui.control.buttonIcon import ButtonIcon
     23 from eve.client.script.ui.control.entries.util import GetFromClass
     24 from eve.client.script.ui.control.themeColored import FillThemeColored, FrameThemeColored
     25 from eve.client.script.ui.shared.inventory import invSettings
     26 from eve.client.script.ui.shared.item import InvItem
     27 from eve.common.script.sys import eveCfg
     28 from eve.common.script.util.eveFormat import GetAveragePrice
     29 from eveexceptions import UserError
     30 from inventorycommon.const import VIEW_MODES, SCROLL_VIEW_MODES, VIEWMODE_DETAILS, VIEWMODE_ICONS, VIEWMODE_LIST, VIEWMODE_CARDS, flagFrigateEscapeBay, flagStructureDeed
     31 from inventorycommon.util import GetItemVolume
     32 from eve.client.script.ui.util import uix
     33 import blue
     34 import carbon.client.script.util.lg as lg
     35 import uthread
     36 import carbonui.const as uiconst
     37 import log
     38 import trinity
     39 import eve.client.script.environment.invControllers as invCtrl
     40 import localization
     41 import telemetry
     42 import functools
     43 from carbonui.uicore import uicore
     44 from menu import MenuLabel
     45 from bannedwords.client import bannedwords
     46 from eve.common.lib import appConst as const
     47 VIEW_MODE_ROW_PADDING = {VIEWMODE_LIST: 0,
     48  VIEWMODE_DETAILS: 0,
     49  VIEWMODE_ICONS: 12,
     50  VIEWMODE_CARDS: 6}
     51 
     52 class InvContViewBtns(ContainerAutoSize):
     53     default_name = 'InvContViewBtns'
     54     default_align = uiconst.TOPLEFT
     55     default_height = 24
     56 
     57     def ApplyAttributes(self, attributes):
     58         ContainerAutoSize.ApplyAttributes(self, attributes)
     59         self.controller = attributes.controller
     60         self.viewModeButtons = {}
     61         for viewMode in self.controller.availableViewModes:
     62             self.viewModeButtons[viewMode] = ButtonIcon(texturePath=self.controller.GetViewModeIcon(viewMode), parent=self, width=self.height, align=uiconst.TOLEFT, func=functools.partial(self._SetViewMode, viewMode), hint=self.controller.GetViewModeName(viewMode))
     63 
     64     def _SetViewMode(self, viewMode):
     65         self.UpdateButtons(viewMode)
     66         self.controller.SetInvContViewMode(viewMode)
     67 
     68     def UpdateButtons(self, activeViewMode):
     69         for viewMode, button in self.viewModeButtons.iteritems():
     70             if viewMode == activeViewMode:
     71                 button.SetSelected()
     72             else:
     73                 button.SetDeselected()
     74 
     75 
     76 class InvContQuickFilter(Container):
     77     default_align = uiconst.TOPLEFT
     78     default_height = 24
     79     default_width = 100
     80 
     81     def ApplyAttributes(self, attributes):
     82         Container.ApplyAttributes(self, attributes)
     83         self.invCont = attributes.invCont
     84         self.inputThread = None
     85         self.quickFilterInputBox = SingleLineEditText(name='quickFilterInputBox', parent=self, align=uiconst.CENTER, width=self.width, height=24, top=0, density=Density.COMPACT, OnChange=self.SetQuickFilterInput, isTypeField=True, hintText=localization.GetByLabel('UI/Common/Search'))
     86         self.quickFilterInputBox.ShowClearButton(hint=localization.GetByLabel('UI/Inventory/Clear'))
     87         self.quickFilterInputBox.SetHistoryVisibility(0)
     88         if self.invCont:
     89             self.SetInvCont(self.invCont)
     90 
     91     def SetInvCont(self, invCont):
     92         self.invCont = invCont
     93         if not invSettings.keep_inv_quick_filter_setting.is_enabled():
     94             self.quickFilterInputBox.SetText(u'')
     95             self.quickFilterInputBox.ClearSelection()
     96 
     97     def GetQuickFilterInput(self):
     98         return self.quickFilterInputBox.GetText()
     99 
    100     def SetQuickFilterInput(self, txt):
    101         if self.inputThread:
    102             return
    103         self.inputThread = uthread.new(self._SetQuickFilterInput, txt)
    104 
    105     def ClearFilter(self):
    106         self.quickFilterInputBox.SetValue('')
    107 
    108     def _SetQuickFilterInput(self, txt):
    109         if txt:
    110             blue.synchro.SleepWallclock(300)
    111         if self.invCont:
    112             self.invCont.SetQuickFilterInput(self.quickFilterInputBox.GetValue())
    113         self.inputThread = None
    114 
    115     def UpdateInputBoxAlign(self, newAlign):
    116         self.quickFilterInputBox.SetAlign(newAlign)
    117 
    118 
    119 class InvContCapacityGauge(Container):
    120     __notifyevents__ = ['OnItemChange', 'OnSessionChanged', 'OnSessionMutated']
    121     default_align = uiconst.TOPLEFT
    122     default_state = uiconst.UI_NORMAL
    123     default_clipChildren = True
    124 
    125     def ApplyAttributes(self, attributes):
    126         Container.ApplyAttributes(self, attributes)
    127         sm.RegisterNotify(self)
    128         invCont = attributes.invCont
    129         self.secondaryVolume = 0.0
    130         self.additionalVolume = 0.0
    131         self.refreshGaugesThread = None
    132         self.resetPending = False
    133         self.refreshCapacityPending = False
    134         self.queuedItemChanges = []
    135         self.capacityText = eveLabel.EveLabelSmall(name='capacityText', parent=self, align=uiconst.CENTERLEFT, top=1, left=8)
    136         self.bg = Fill(bgParent=self, color=(0.0, 0.0, 0.0), opacity=0.4)
    137         self.capacityGaugeParentSec = Container(name='capacityGaugeParent', parent=self, align=uiconst.TOALL, state=uiconst.UI_DISABLED)
    138         self.capacityGaugeSec = Fill(parent=self.capacityGaugeParentSec, align=uiconst.TOLEFT_PROP, color=self._get_gauge_highlight_color())
    139         self.capacityGaugeParent = Container(name='capacityGaugeParent', parent=self, align=uiconst.TOALL, state=uiconst.UI_DISABLED)
    140         self.capacityGauge = Fill(parent=self.capacityGaugeParent, align=uiconst.TOLEFT_PROP, color=self._get_gauge_color())
    141         self.capacityGaugeAdd = Fill(parent=self.capacityGaugeParent, align=uiconst.TOLEFT_PROP, color=self._get_gauge_highlight_color())
    142         if invCont:
    143             self.SetInvCont(invCont)
    144         else:
    145             self.invCont = None
    146 
    147     @staticmethod
    148     def _get_gauge_color():
    149         return tuple(eveThemeColor.THEME_FOCUSDARK[:3]) + (0.6,)
    150 
    151     @staticmethod
    152     def _get_gauge_highlight_color():
    153         return tuple(eveThemeColor.THEME_FOCUS[:3]) + (0.6,)
    154 
    155     def SetInvCont(self, invCont):
    156         self.invCont = invCont
    157         self.SetSecondaryVolume()
    158         self.SetAdditionalVolume()
    159         self.RefreshCapacity()
    160 
    161     @telemetry.ZONE_METHOD
    162     def RefreshCapacity(self):
    163         if not session.IsItSafe():
    164             self.refreshCapacityPending = True
    165             return
    166         self.refreshCapacityPending = False
    167         if not self.invCont:
    168             return
    169         self.UpdateLabel()
    170         proportion = 0.0
    171         if self.invCont.invController.hasCapacity:
    172             cap = self.invCont.invController.GetCapacity()
    173             if cap.capacity:
    174                 proportion = min(1.0, max(0.0, cap.used / float(cap.capacity)))
    175         currWidth = self.capacityGauge.width
    176         duration = 0.5 * fabs(currWidth - proportion) ** 0.3
    177         uicore.animations.MorphScalar(self.capacityGauge, 'width', currWidth, proportion, duration=duration)
    178 
    179     def UpdateLabel(self):
    180         currentInvCont = self.invCont
    181         if currentInvCont is None or currentInvCont.destroyed:
    182             return
    183         if currentInvCont.invController.hasCapacity:
    184             cap = currentInvCont.invController.GetCapacity()
    185             volume = cap.used + self.additionalVolume
    186             text = localization.GetByLabel('UI/Inventory/ContainerQuantityAndCapacity', quantity=volume, capacity=cap.capacity)
    187         else:
    188             volume = 0.0
    189             for item in currentInvCont.invController.GetItems():
    190                 volume += GetItemVolume(item)
    191 
    192             text = localization.GetByLabel('UI/Inventory/ContainerCapacity', capacity=volume)
    193         if currentInvCont is None or currentInvCont.destroyed:
    194             return
    195         if self.secondaryVolume:
    196             if text:
    197                 text = '(%s) ' % localization.formatters.FormatNumeric(self.secondaryVolume, useGrouping=True, decimalPlaces=1) + text
    198         if currentInvCont.invController.locationFlag == flagFrigateEscapeBay:
    199             text = localization.GetByLabel('UI/Inventory/FrigateEscapeBayQuantityAndCapacity', quantity=volume, capacity=cap.capacity)
    200         elif currentInvCont.invController.locationFlag == flagStructureDeed:
    201             text = localization.GetByLabel('UI/Inventory/DeedBayQuantityAndCapacity', quantity=1 if volume else 0, capacity=1)
    202         self.capacityText.text = text
    203 
    204     def HideLabel(self):
    205         self.capacityText.Hide()
    206 
    207     def ShowLabel(self):
    208         self.capacityText.Show()
    209 
    210     def GetHint(self):
    211         return self.capacityText.text
    212 
    213     @telemetry.ZONE_METHOD
    214     def SetAdditionalVolume(self, items = []):
    215         if not self.invCont:
    216             return
    217         volume = 0
    218         for item in items:
    219             volume += GetItemVolume(item)
    220 
    221         self.additionalVolume = volume
    222         value = self.GetVolumeProportion(volume)
    223         animValue = min(value, 1.0 - self.capacityGauge.width)
    224         currWidth = self.capacityGaugeAdd.width
    225         duration = 0.5 * fabs(currWidth - animValue) ** 0.3
    226         uicore.animations.MorphScalar(self.capacityGaugeAdd, 'width', currWidth, animValue, duration=duration)
    227         color = self._get_gauge_highlight_color()
    228         if self.invCont.invController.hasCapacity:
    229             cap = self.invCont.invController.GetCapacity()
    230             if cap.capacity and volume + cap.used > cap.capacity:
    231                 color = eveColor.WARNING_ORANGE[:3]
    232         self.capacityGaugeAdd.SetRGBA(*color)
    233         self.UpdateLabel()
    234 
    235     @telemetry.ZONE_METHOD
    236     def SetSecondaryVolume(self, items = []):
    237         self.secondaryVolume = volume = sum([ GetItemVolume(i) for i in items ])
    238         value = self.GetVolumeProportion(volume)
    239         currWidth = self.capacityGaugeSec.width
    240         duration = 0.5 * fabs(currWidth - value) ** 0.3
    241         uicore.animations.MorphScalar(self.capacityGaugeSec, 'width', currWidth, value, duration=duration)
    242         self.UpdateLabel()
    243 
    244     def GetVolumeProportion(self, volume):
    245         if self.invCont and self.invCont.invController.hasCapacity:
    246             cap = self.invCont.invController.GetCapacity()
    247             if cap.capacity:
    248                 return min(1.0, volume / cap.capacity)
    249         return 0
    250 
    251     def OnItemChange(self, item, change, location):
    252         itemID = item.itemID
    253         sourceLocationID = item.locationID
    254         destinationLocationID = change[const.ixLocationID] if const.ixLocationID in change else item.locationID
    255         if not session.IsItSafe():
    256             self.queuedItemChanges.append((itemID, sourceLocationID, destinationLocationID))
    257             return
    258         if self._ShouldResetGauges(itemID, sourceLocationID, destinationLocationID):
    259             self.ResetGauges()
    260 
    261     def _ShouldResetGauges(self, itemID, sourceLocationID, destinationLocationID):
    262         if not self.invCont or itemID == eveCfg.GetActiveShip():
    263             return False
    264         return self.invCont.invController.itemID in (sourceLocationID, destinationLocationID)
    265 
    266     def _ApplyItemChangesReceivedDuringSessionChange(self):
    267         if not session.IsItSafe():
    268             return
    269         try:
    270             for itemID, sourceLocationID, destinationLocationID in self.queuedItemChanges:
    271                 if self._ShouldResetGauges(itemID, sourceLocationID, destinationLocationID):
    272                     self.ResetGauges()
    273                     break
    274 
    275             if self.refreshCapacityPending:
    276                 self.RefreshCapacity()
    277         finally:
    278             self.queuedItemChanges = []
    279 
    280     def OnSessionChanged(self, *args, **kwargs):
    281         self._ApplyItemChangesReceivedDuringSessionChange()
    282 
    283     def OnSessionMutated(self, *args, **kwargs):
    284         self._ApplyItemChangesReceivedDuringSessionChange()
    285 
    286     def ResetGauges(self):
    287         self.resetPending = True
    288         if self.refreshGaugesThread:
    289             return
    290         self.refreshGaugesThread = uthread.new(self._ResetGauges)
    291 
    292     def _ResetGauges(self):
    293         try:
    294             while self.resetPending:
    295                 self.RefreshCapacity()
    296                 nodes = self.invCont.scroll.GetSelected()
    297                 self.SetSecondaryVolume([ node.item for node in nodes ])
    298                 self.SetAdditionalVolume()
    299                 self.resetPending = False
    300                 blue.synchro.Sleep(500)
    301 
    302         finally:
    303             self.refreshGaugesThread = None
    304 
    305     def OnColorThemeChanged(self):
    306         if self.capacityGauge:
    307             self.capacityGauge.color = self._get_gauge_color()
    308         if self.capacityGaugeSec:
    309             self.capacityGaugeSec.color = self._get_gauge_highlight_color()
    310         if self.capacityGaugeAdd:
    311             self.capacityGaugeAdd.color = self._get_gauge_highlight_color()
    312 
    313 
    314 class ItemSortData(object):
    315 
    316     def __init__(self, rec):
    317         self.rec = rec
    318         self.name = StripTags(uix.GetItemName(rec).lower())
    319         self.typeName = StripTags(uix.GetCategoryGroupTypeStringForItem(rec).lower())
    320         self.itemID = rec.itemID
    321         self.quantity = 0
    322         if not (rec.singleton or rec.typeID in (const.typeBookmark,)):
    323             self.quantity = rec.stacksize
    324 
    325     def GetSortKey(self, sortby, direction):
    326         if sortby == 'name':
    327             return (self.name,
    328              self.typeName,
    329              self.quantity,
    330              self.itemID,
    331              self.rec)
    332         if sortby == 'qty':
    333             return (self.quantity,
    334              self.typeName,
    335              self.name,
    336              self.itemID,
    337              self.rec)
    338         if sortby == 'type':
    339             return (self.typeName,
    340              self.name,
    341              self.quantity,
    342              self.itemID,
    343              self.rec)
    344         if sortby == 'estPrice':
    345             estimatedPrice = self.GetEstimatedPrice()
    346             return (estimatedPrice,
    347              self.name,
    348              self.typeName,
    349              self.quantity,
    350              self.itemID,
    351              self.rec)
    352 
    353     def GetEstimatedPrice(self):
    354         if evetypes.GetCategoryID(self.rec.typeID) == const.categoryBlueprint:
    355             return None
    356         averagePrice = GetAveragePrice(self.rec)
    357         if not averagePrice:
    358             return None
    359         return self.rec.stacksize * averagePrice
    360 
    361 
    362 class _InvContBase(Container):
    363     __guid__ = 'invCont._InvContBase'
    364     default_name = 'InventoryContainer'
    365     default_showControls = False
    366     default_containerViewMode = VIEWMODE_ICONS
    367     default_availableViewModes = (VIEWMODE_ICONS, VIEWMODE_DETAILS, VIEWMODE_LIST)
    368     default_hasScrollBackground = False
    369     default_shouldAddFinalRowPadding = True
    370     default_scrollClass = eveScroll.Scroll
    371     default_selectAllOnFirstLoad = False
    372     __notifyevents__ = ['OnPostCfgDataChanged', 'OnInvTempFilterChanged']
    373     __invControllerClass__ = None
    374 
    375     def ApplyAttributes(self, attributes):
    376         Container.ApplyAttributes(self, attributes)
    377         sm.RegisterNotify(self)
    378         self.itemID = attributes.itemID
    379         self.displayName = attributes.displayName
    380         self.activeFilters = attributes.get('activeFilters', [])
    381         showControls = attributes.get('showControls', self.default_showControls)
    382         self.quickFilterInput = attributes.Get('quickFilterInput', None)
    383         self.hasScrollBackground = attributes.get('hasScrollBackground', self.default_hasScrollBackground)
    384         self.shouldAddFinalRowPadding = attributes.get('shouldAddFinalRowPadding', self.default_shouldAddFinalRowPadding)
    385         self.scrollClass = attributes.get('scrollClass', self.default_scrollClass)
    386         self.selectAllOnFirstLoad = attributes.get('selectAllOnFirstLoad', self.default_selectAllOnFirstLoad)
    387         self.availableViewModes = attributes.get('availableViewModes', self.default_availableViewModes)
    388         self.getItemClass = attributes.get('getItemClass', self.GetItemClass)
    389         self.invController = self._GetInvController(attributes)
    390         self.scroll = None
    391         self.items = []
    392         self.cols = None
    393         self.droppedIconData = (None, None)
    394         self.iconWidth = 64
    395         self.sr.resizeTimer = None
    396         self.tmpDropIdx = {}
    397         self.refreshingView = False
    398         self.reRefreshView = False
    399         self.changeViewModeThread = None
    400         self.hintText = None
    401         self.dragStart = None
    402         self.previouslyHighlighted = None
    403         self.dragContainer = None
    404         self.itemChangeThread = None
    405         self.initialized = False
    406         self.numFilteredItems = 0
    407         self.hasLoaded = False
    408         self.view_mode_setting = UserSettingEnum(settings_key=self.GetViewModeSettingKey(), options=VIEW_MODES, default_value=self.default_containerViewMode)
    409         self.rowPadding = 0
    410         self.colMargin = 16
    411         self._SetViewModeValue(self.view_mode_setting.get())
    412         if showControls:
    413             self.topCont = Container(parent=self, align=uiconst.TOTOP, height=24, padBottom=8)
    414             InvContViewBtns(parent=self.topCont, align=uiconst.CENTERLEFT, controller=self)
    415             if self.IsQuickFilterEnabled():
    416                 InvContQuickFilter(parent=self.topCont, align=uiconst.CENTERRIGHT, invCont=self)
    417         self.ConstructUI()
    418 
    419     def ConstructUI(self):
    420         self.scroll = self.scrollClass(parent=self, state=uiconst.UI_PICKCHILDREN, hasUnderlay=self.hasScrollBackground, rowPadding=self.rowPadding, shouldAddFinalRowPadding=self.shouldAddFinalRowPadding)
    421         self.scroll.sr.id = self._GetIDForScroll()
    422         self.scroll.OnNewHeaders = self.OnNewHeadersSet
    423         self.scroll.allowFilterColumns = 1
    424         self.scroll.SetColumnsHiddenByDefault(uix.GetInvItemDefaultHiddenHeaders())
    425         self.scroll.dad = self
    426         self.scroll.Copy = self.Copy
    427         self.scroll.Cut = self.Cut
    428         self.scroll.Paste = self.Paste
    429         content = self.content = self.scroll.GetContentContainer()
    430         content.OnDragEnter = self.OnDragEnter
    431         content.OnDragExit = self.OnDragExit
    432         content.OnDropData = self.OnScrollContentDropData
    433         content.GetMenu = lambda : self.GetContainerMenu()
    434         content.OnMouseUp = self.scroll.OnMouseUp = self.OnScrollMouseUp
    435         content.OnMouseDown = self.scroll.OnMouseDown = self.OnScrollMouseDown
    436         self.invSvcCookie = sm.GetService('inv').Register(self)
    437         self.InitializeSortBy()
    438         uthread.new(self._ChangeViewMode)
    439         self.view_mode_setting.on_change.connect(self._on_view_mode_setting_changed)
    440 
    441     def _GetIDForScroll(self):
    442         return 'containerWnd__%s' % self.invController.GetName()
    443 
    444     def _on_view_mode_setting_changed(self, view_mode):
    445         if self.viewMode != view_mode:
    446             self._SetViewModeValue(view_mode)
    447             self._ChangeViewMode()
    448 
    449     def InitializeSortBy(self):
    450         self.sortIconsBy, self.sortIconsDir = settings.char.ui.Get('containerSortIconsBy_%s' % self.name, ('type', 0))
    451 
    452     def _GetInvController(self, attributes):
    453         return self.__invControllerClass__(itemID=attributes.itemID)
    454 
    455     def GetItemClass(self):
    456         return InvItem
    457 
    458     def GetViewModeSettingKey(self):
    459         return u'containerViewMode_{}'.format(self.name)
    460 
    461     def SetInvContViewMode(self, value):
    462         self._SetViewModeValue(value)
    463         self.view_mode_setting.set(self.viewMode)
    464         self._ChangeViewMode()
    465 
    466     def _SetViewModeValue(self, value):
    467         if value in self.availableViewModes:
    468             self.viewMode = value
    469         else:
    470             self.viewMode = self.availableViewModes[0] if self.availableViewModes else None
    471         self.itemClass = self.getItemClass()
    472         self.itemHeight = self.itemClass.GetEntryHeight()
    473         self.itemWidth = self.itemClass.GetEntryWidth()
    474         self.colMargin = self.itemClass.GetEntryColMargin()
    475         self.rowPadding = VIEW_MODE_ROW_PADDING.get(self.viewMode, 6)
    476         if self.scroll:
    477             self.scroll.rowPadding = self.rowPadding
    478         self.cols = None
    479 
    480     def IsViewModesEnabled(self):
    481         return bool(self.availableViewModes)
    482 
    483     def IsQuickFilterEnabled(self):
    484         return True
    485 
    486     def IsCapacityEnabled(self):
    487         return True
    488 
    489     def IsEstimatedValueEnabled(self):
    490         return True
    491 
    492     def SetQuickFilterInput(self, filterTxt = ''):
    493         if len(filterTxt) > 0:
    494             self.quickFilterInput = filterTxt.lower()
    495             self.Refresh()
    496         else:
    497             prefilter = self.quickFilterInput
    498             self.quickFilterInput = None
    499             if prefilter != None:
    500                 self.Refresh()
    501             self.hintText = None
    502 
    503     def QuickFilter(self, item):
    504         name = uix.GetItemName(item).lower()
    505         typename = evetypes.GetName(item.typeID).lower()
    506         input = self.quickFilterInput.lower()
    507         return name.find(input) + 1 or typename.find(input) + 1
    508 
    509     def OnInvTempFilterChanged(self):
    510         self.Refresh()
    511 
    512     def SetFilters(self, filters):
    513         self.activeFilters = filters
    514         self.Refresh()
    515 
    516     def FilterOptionsChange(self, combo, label, value, *args):
    517         if combo and combo.name == 'filterCateg':
    518             if value is None:
    519                 ops = [(localization.GetByLabel('UI/Inventory/Filters/All'), None)]
    520             else:
    521                 ops = sm.GetService('marketutils').GetFilterops(value)
    522             self.sr.filterGroup.LoadOptions(ops)
    523             settings.user.ui.Set('containerCategoryFilter%s' % self.name.title(), value)
    524         elif combo and combo.name == 'filterGroup':
    525             settings.user.ui.Set('containerGroupFilter%s' % self.name.title(), value)
    526         self.Refresh()
    527 
    528     def OnPostCfgDataChanged(self, what, data):
    529         if what == 'evelocations':
    530             itemID = data[0]
    531             for each in self.scroll.GetNodes():
    532                 if each and getattr(each, 'item', None) and each.item.itemID == itemID:
    533                     each.name = None
    534                     uix.GetItemLabel(each.item, each, 1)
    535                     if each.panel:
    536                         each.panel.UpdateLabel()
    537                     return
    538 
    539     def _OnClose(self, *args):
    540         if self.itemChangeThread:
    541             self.itemChangeThread.kill()
    542 
    543     def _ChangeViewMode(self):
    544         if self.changeViewModeThread:
    545             self.changeViewModeThread.kill()
    546         self.changeViewModeThread = uthread.new(self.__ChangeViewMode)
    547 
    548     def __ChangeViewMode(self):
    549         if self.destroyed:
    550             return
    551         self.items = self.invController.GetItems()
    552         self.invController.OnItemsViewed()
    553         self.PrimeItems([ item for item in self.items if item is not None ])
    554         if self.items:
    555             self.items = self.FilterItems(self.items)
    556         self.UpdateHint()
    557         try:
    558             if self.ShouldShowSortBy():
    559                 self.RefreshSort(self.sortIconsBy, self.sortIconsDir)
    560             else:
    561                 self.RefreshView()
    562         except UserError:
    563             self.Close()
    564             if self.sr.Get('cookie', None):
    565                 sm.GetService('inv').Unregister(self.invSvcCookie)
    566             raise
    567 
    568         if self.selectAllOnFirstLoad and not self.hasLoaded:
    569             self.SelectAll()
    570         self.hasLoaded = True
    571 
    572     def IsInItems(self, itemID):
    573         for rec in self.items:
    574             if rec and rec.itemID == itemID:
    575                 return True
    576 
    577         return False
    578 
    579     def FilterItems(self, items):
    580         oldNum = len(items)
    581         if self.quickFilterInput:
    582             bannedwords.check_search_words_allowed(self.quickFilterInput)
    583             items = NiceFilter(self.QuickFilter, items)
    584         for filter in self.activeFilters:
    585             items = sm.GetService('itemFilter').FilterItems(items, filter)
    586 
    587         if self.invController.filtersEnabled:
    588             items = sm.GetService('itemFilter').ApplyTempFilter(items)
    589         self.numFilteredItems = oldNum - len(items)
    590         return items
    591 
    592     def UpdateHint(self):
    593         if len(self.items) == 0:
    594             if self.numFilteredItems:
    595                 self.hintText = localization.GetByLabel('UI/Inventory/NothingFoundFiltered', numFilters=self.numFilteredItems)
    596             elif not self.invController.CheckCanQuery():
    597                 self.hintText = localization.GetByLabel('UI/Inventory/NoAccessHint')
    598             elif self.invController.isCompact and self.invController.GetName():
    599                 self.hintText = localization.GetByLabel('UI/Common/NothingFoundInContainer', invContainerName=self.invController.GetName())
    600             else:
    601                 self.hintText = localization.GetByLabel('UI/Common/NothingFound')
    602         else:
    603             self.hintText = ''
    604         self.scroll.ShowHint(self.hintText)
    605 
    606     def Refresh(self):
    607         self._ChangeViewMode()
    608 
    609     def GetSortData(self, sortby, direction):
    610         sortData = []
    611         for rec in self.items:
    612             if rec is None:
    613                 continue
    614             sortKey = ItemSortData(rec).GetSortKey(sortby, direction)
    615             if not sortKey:
    616                 log.LogError('Unknown sortkey used in container sorting', sortby, direction)
    617                 continue
    618             sortData.append((sortKey, rec))
    619 
    620         return sortData
    621 
    622     def RefreshSort(self, sortby, direction):
    623         self.sortIconsBy = sortby
    624         self.sortIconsDir = direction
    625         settings.char.ui.Set('containerSortIconsBy_%s' % self.name, (sortby, direction))
    626         sortData = self.GetSortData(sortby, direction)
    627         locCmpFunc = localization.util.GetSortFunc(localization.util.GetLanguageID())
    628 
    629         def _sort(left, right):
    630             if len(left) == 0:
    631                 return 0
    632             if isinstance(left[0], basestring):
    633                 res = locCmpFunc(left[0], right[0])
    634             else:
    635                 res = cmp(left[0], right[0])
    636             if res == 0:
    637                 return _sort(left[1:], right[1:])
    638             return res
    639 
    640         sortedList = sorted(sortData, cmp=_sort, key=lambda x: x[0])
    641         sortedList = [ x[1] for x in sortedList ]
    642         if direction:
    643             sortedList.reverse()
    644         self.SetSortedItems(sortedList)
    645 
    646     def SetSortedItems(self, sortedList):
    647         self.items = sortedList
    648         self.RefreshView()
    649 
    650     def SortIconsBy(self, sortby, direction):
    651         self.RefreshSort(sortby, direction)
    652 
    653     def _OnSizeChange_NoBlock(self, *args):
    654         self.sr.resizeTimer = timerstuff.AutoTimer(250, self.OnEndScale_)
    655 
    656     def OnEndScale_(self, *etc):
    657         if self.destroyed:
    658             self.sr.resizeTimer = None
    659             return
    660         if not self.initialized:
    661             return
    662         self.sr.resizeTimer = None
    663         oldcols = self.cols
    664         self.RefreshCols()
    665         if self.viewMode not in SCROLL_VIEW_MODES:
    666             if self.refreshingView:
    667                 self.reRefreshView = True
    668                 return
    669             if oldcols != self.cols or not self.itemClass.IsFixedWidth():
    670                 uthread.new(self.RefreshView)
    671 
    672     @telemetry.ZONE_METHOD
    673     def AddItem(self, rec, index = None, fromWhere = None):
    674         if self.quickFilterInput:
    675             if not self.QuickFilter(rec):
    676                 return
    677         if not self.FilterItems([rec]):
    678             return
    679         lg.Info('vcont', 'AddItem', fromWhere, rec.stacksize, evetypes.GetName(rec.typeID))
    680         for node in self.scroll.sr.nodes:
    681             if self.viewMode in SCROLL_VIEW_MODES:
    682                 if node is not None and node.Get('item', None) and node.item.itemID == rec.itemID:
    683                     lg.Warn('vcont', 'Tried to add an item that is already there??')
    684                     self.UpdateItem(node.item)
    685                     return
    686             else:
    687                 for internalNode in node.internalNodes:
    688                     if internalNode is not None and internalNode.item.itemID == rec.itemID:
    689                         lg.Warn('vcont', 'Tried to add an item that is already there??')
    690                         self.UpdateItem(internalNode.item)
    691                         return
    692 
    693         if self.viewMode in SCROLL_VIEW_MODES:
    694             from eve.client.script.ui.shared.item import Item
    695             self.items.append(rec)
    696             data = self.GetItemData(rec, self.scroll.sr.id)
    697             self.scroll.AddEntries(-1, [GetFromClass(Item, data)])
    698             self.UpdateHint()
    699         else:
    700             if index is not None:
    701                 while index < len(self.items):
    702                     if self.items[index] is None:
    703                         return self.SetItem(index, rec)
    704                     index += 1
    705 
    706                 while index >= len(self.scroll.sr.nodes) * self.cols:
    707                     self.AddRow()
    708 
    709                 return self.SetItem(index, rec)
    710             if len(self.items) and None in self.items:
    711                 idx = self.tmpDropIdx.get(rec.itemID, None)
    712                 if idx is None:
    713                     idx = self.items.index(None)
    714                 return self.SetItem(idx, rec)
    715             if not self.cols:
    716                 self.RefreshCols()
    717             if index >= len(self.scroll.sr.nodes) * self.cols:
    718                 self.AddRow()
    719             return self.SetItem(0, rec)
    720 
    721     @telemetry.ZONE_METHOD
    722     def UpdateItem(self, rec, *etc):
    723         lg.Info('vcont', 'UpdateItem', rec and '[%s %s]' % (rec.stacksize, evetypes.GetName(rec.typeID)))
    724         if self.viewMode in SCROLL_VIEW_MODES:
    725             idx = 0
    726             for each in self.items:
    727                 if each.itemID == rec.itemID:
    728                     self.items[idx] = rec
    729                     break
    730                 idx += 1
    731 
    732             for entry in self.scroll.sr.nodes:
    733                 if entry.item.itemID == rec.itemID:
    734                     newentry = self.GetItemData(rec, self.scroll.sr.id)
    735                     for key, value in newentry.iteritems():
    736                         entry[key] = value
    737 
    738                     if entry.panel:
    739                         entry.panel.Load(entry)
    740                     self.UpdateHint()
    741                     return
    742 
    743         else:
    744             i = 0
    745             for rowNode in self.scroll.sr.nodes:
    746                 for entry in rowNode.internalNodes:
    747                     if entry is not None and entry.item and entry.item.itemID == rec.itemID:
    748                         self.SetItem(i, rec)
    749                         return
    750                     i += 1
    751 
    752     def GetItemData(self, rec, scrollID = None):
    753         return uix.GetItemData(rec, self.viewMode, self.invController.viewOnly, container=self, scrollID=scrollID)
    754 
    755     @telemetry.ZONE_METHOD
    756     def RemoveItem(self, rec):
    757         lg.Info('vcont', 'RemoveItem', rec and '[%s %s]' % (rec.stacksize, evetypes.GetName(rec.typeID)))
    758         if self.viewMode in SCROLL_VIEW_MODES:
    759             for entry in self.scroll.sr.nodes:
    760                 if entry.item.itemID == rec.itemID:
    761                     self.scroll.RemoveEntries([entry])
    762                     break
    763 
    764             for item in self.items:
    765                 if item.itemID == rec.itemID:
    766                     self.items.remove(item)
    767 
    768         else:
    769             i = 0
    770             for rowNode in self.scroll.sr.nodes:
    771                 si = 0
    772                 for entry in rowNode.internalNodes:
    773                     if entry and entry.item and entry.item.itemID == rec.itemID:
    774                         self.SetItem(i, None)
    775                         rowNode.internalNodes[si] = None
    776                         break
    777                     si += 1
    778                     i += 1
    779 
    780             i = 0
    781             for item in self.items:
    782                 if item and item.itemID == rec.itemID:
    783                     self.items[i] = None
    784                 i += 1
    785 
    786             self.CleanupRows()
    787 
    788     @telemetry.ZONE_METHOD
    789     def CleanupRows(self):
    790         rm = []
    791         for rowNode in self.scroll.sr.nodes:
    792             internalNodes = rowNode.Get('internalNodes', [])
    793             if internalNodes == [None] * len(internalNodes):
    794                 rm.append(rowNode)
    795             else:
    796                 rm = []
    797 
    798         if rm:
    799             self.scroll.RemoveEntries(rm)
    800 
    801     @telemetry.ZONE_METHOD
    802     def AddRow(self):
    803         self.items += [None] * self.cols
    804         self.scroll.AddEntries(fromIdx=-1, nodesData=[GetFromClass(Row, {'lenitems': len(self.scroll.sr.nodes) * self.cols,
    805           'rec': [None] * self.cols,
    806           'internalNodes': [None] * self.cols,
    807           'parentWindow': self,
    808           'hilightable': False,
    809           'container': self,
    810           'itemClass': self.itemClass,
    811           'itemWidth': self.itemWidth,
    812           'itemHeight': self.itemHeight})])
    813         self.scroll.UpdatePosition()
    814 
    815     @telemetry.ZONE_METHOD
    816     def StackAll(self):
    817         self.invController.StackAll()
    818         uthread.new(self.Refresh)
    819 
    820     def OnScrollContentDropData(self, dragObj, nodes):
    821         idx = None
    822         if self.viewMode not in SCROLL_VIEW_MODES and self.cols:
    823             l, t = self.scroll.GetAbsolutePosition()
    824             idx = self.cols * len(self.scroll.sr.nodes) + (uicore.uilib.x - l) // (64 + self.colMargin)
    825         sm.ScatterEvent('OnInvContDragExit', self.invController.GetInvID(), [])
    826         self.OnDropDataWithIdx(nodes, idx)
    827 
    828     def OnDragEnter(self, dragObj, nodes):
    829         sm.ScatterEvent('OnInvContDragEnter', self.invController.GetInvID(), nodes)
    830 
    831     def OnDragExit(self, dragObj, nodes):
    832         sm.ScatterEvent('OnInvContDragExit', self.invController.GetInvID(), nodes)
    833 
    834     def OnDropData(self, dragObj, nodes):
    835         return self.OnDropDataWithIdx(nodes)
    836 
    837     def Cut(self):
    838         items = self.scroll.GetSelected()
    839         sm.GetService('inv').SetItemClipboard(items)
    840 
    841     def Copy(self):
    842         if bool(session.role & ROLE_PROGRAMMER):
    843             items = self.scroll.GetSelected()
    844             sm.GetService('inv').SetItemClipboard(items, copy=True)
    845         return eveScroll.Scroll.Copy(self.scroll)
    846 
    847     def Paste(self, value):
    848         items, copy = sm.GetService('inv').PopItemClipboard()
    849         if copy and bool(session.role & ROLE_PROGRAMMER):
    850             from eve.devtools.script import param
    851             for item in items:
    852                 itemID = sm.GetService('slash').cmd_createitem(param.ParamObject('%s %s' % (item.rec.typeID, item.rec.stacksize)))
    853                 if itemID:
    854                     invController = invCtrl.StationItems() if session.stationid else invCtrl.ShipCargo()
    855                     blue.synchro.SleepWallclock(100)
    856                     newItem = invController.GetItem(itemID)
    857                     if newItem:
    858                         self.invController.AddItems([newItem])
    859 
    860         else:
    861             self.invController.OnDropData(items)
    862 
    863     @telemetry.ZONE_METHOD
    864     def OnDropDataWithIdx(self, nodes, idx = None):
    865         self.scroll.ClearSelection()
    866         if len(nodes) and getattr(nodes[0], 'scroll', None) and not nodes[0].scroll.destroyed:
    867             nodes[0].scroll.ClearSelection()
    868         if nodes and getattr(nodes[0], 'item', None) in self.invController.GetItems() and not uicore.uilib.Key(uiconst.VK_SHIFT):
    869             if getattr(nodes[0], 'scroll', None) != self.scroll:
    870                 return
    871             if idx is not None:
    872                 for i, node in enumerate(nodes):
    873                     self.tmpDropIdx[node.itemID] = idx + i
    874 
    875             for node in nodes:
    876                 idx = self.tmpDropIdx.get(node.itemID, None)
    877                 if idx is None:
    878                     if None in self.items:
    879                         idx = self.items.index(None)
    880                     else:
    881                         idx = len(self.items)
    882                 self.OnItemDrop(idx, node)
    883 
    884             return
    885         return self.invController.OnDropData(nodes)
    886 
    887     @telemetry.ZONE_METHOD
    888     def OnItemDrop(self, index, node):
    889         if self.viewMode == VIEWMODE_ICONS:
    890             self.RemoveItem(node.item)
    891             self.AddItem(node.item, index)
    892 
    893     @telemetry.ZONE_METHOD
    894     def SetItem(self, index, rec):
    895         lg.Info('vcont', 'SetItem', index, rec and '[%s %s]' % (rec.stacksize, evetypes.GetName(rec.typeID)))
    896         if not self or self.destroyed:
    897             return
    898         if index < len(self.items) and rec and self.items[index] is not None and self.items[index].itemID != rec.itemID:
    899             while index < len(self.items) and self.items[index] is not None:
    900                 index += 1
    901 
    902         if self.cols:
    903             rowIndex = index // self.cols
    904         else:
    905             rowIndex = 0
    906         while rowIndex >= len(self.scroll.sr.nodes):
    907             self.AddRow()
    908 
    909         while index >= len(self.items):
    910             self.items += [None]
    911 
    912         self.items[index] = rec
    913         try:
    914             self.scroll.sr.nodes[rowIndex].rec[index % self.cols] = rec
    915             self.UpdateHint()
    916             node = None
    917             if rec:
    918                 node = self.GetItemData(rec, self.scroll.sr.id)
    919                 if not self or self.destroyed:
    920                     return
    921                 node.scroll = self.scroll
    922                 node.panel = None
    923                 node.idx = index
    924                 node.__guid__ = self.itemClass.__guid__
    925                 node.decoClass = self.itemClass
    926                 node.width = self.itemWidth
    927                 node.height = self.itemHeight
    928             self.scroll.sr.nodes[index // self.cols].internalNodes[index % self.cols] = node
    929         except IndexError:
    930             return
    931 
    932         icon = self.GetIcon(index)
    933         if icon:
    934             if rec is None:
    935                 icon.display = False
    936                 icon.sr.node = None
    937             else:
    938                 icon.display = True
    939                 node.panel = icon
    940                 node.viewOnly = self.invController.viewOnly
    941                 icon.Load(node)
    942 
    943     @telemetry.ZONE_METHOD
    944     def RefreshCols(self):
    945         w = self.scroll.GetContentWidth()
    946         columns = w // (self.itemWidth + self.colMargin)
    947         if w % (self.itemWidth + self.colMargin) >= self.itemWidth:
    948             columns += 1
    949         self.cols = max(1, columns)
    950 
    951     def PrimeItems(self, itemlist):
    952         locations = []
    953         for rec in itemlist:
    954             if rec.categoryID == const.categoryStation and rec.groupID == const.groupStation:
    955                 locations.append(rec.itemID)
    956                 locations.append(rec.locationID)
    957             if rec.singleton and (rec.categoryID == const.categoryShip or rec.groupID in (const.groupWreck,
    958              const.groupCargoContainer,
    959              const.groupSecureCargoContainer,
    960              const.groupAuditLogSecureContainer,
    961              const.groupFreightContainer,
    962              const.groupBiomass)):
    963                 locations.append(rec.itemID)
    964 
    965         if locations:
    966             cfg.evelocations.Prime(locations)
    967 
    968     def OnNewHeadersSet(self, *args):
    969         self.RefreshView()
    970 
    971     @telemetry.ZONE_METHOD
    972     def RefreshView(self, *args):
    973         from eve.client.script.ui.shared.item import Item
    974         if self.refreshingView:
    975             return
    976         self.refreshingView = 1
    977         try:
    978             if self.viewMode in SCROLL_VIEW_MODES:
    979                 self.scroll.sr.id = self._GetIDForScroll()
    980                 self.scroll.hiliteSorted = 1
    981                 scrolllist = []
    982                 for rec in self.items:
    983                     if rec:
    984                         theData = self.GetItemData(rec, self.scroll.sr.id)
    985                         list = GetFromClass(Item, theData)
    986                         scrolllist.append(list)
    987 
    988                 hdr = uix.GetInvItemDefaultHeaders()
    989                 scrll = self.scroll.GetScrollProportion()
    990                 self.scroll.LoadContent(contentList=scrolllist, headers=hdr, scrollTo=scrll)
    991             else:
    992                 if not self.cols:
    993                     self.RefreshCols()
    994                 while self.items and self.items[-1] is None:
    995                     self.items = self.items[:-1]
    996 
    997                 content = []
    998                 selectedItems = [ node.item for node in self.scroll.GetSelected() ]
    999                 for i in xrange(len(self.items)):
   1000                     blue.pyos.BeNice()
   1001                     row_index = i % self.cols
   1002                     if row_index == 0:
   1003                         entry = [None] * self.cols
   1004                         nodes = [None] * self.cols
   1005                         content.append(GetFromClass(Row, {'lenitems': i,
   1006                          'rec': entry,
   1007                          'internalNodes': nodes,
   1008                          'parentWindow': self,
   1009                          'hilightable': False,
   1010                          'container': self,
   1011                          'itemClass': self.itemClass,
   1012                          'itemWidth': self.itemWidth,
   1013                          'itemHeight': self.itemHeight}))
   1014                     if self.items[i]:
   1015                         node = self.GetItemData(self.items[i])
   1016                         node.scroll = self.scroll
   1017                         node.panel = None
   1018                         node.__guid__ = self.itemClass.__guid__
   1019                         node.decoClass = self.itemClass
   1020                         node.idx = i
   1021                         node.selected = node.item in selectedItems
   1022                         nodes[row_index] = node
   1023                         entry[row_index] = self.items[i]
   1024 
   1025                 self.scroll.sr.sortBy = None
   1026                 self.scroll.sr.id = None
   1027                 self.scroll.LoadContent(fixedEntryHeight=self.itemHeight, contentList=content, scrollTo=self.scroll.GetScrollProportion())
   1028                 self.CleanupRows()
   1029             self.UpdateHint()
   1030             self.initialized = True
   1031             sm.ScatterEvent('OnInvContRefreshed', self)
   1032         finally:
   1033             if not self.destroyed:
   1034                 if self.viewMode == VIEWMODE_DETAILS:
   1035                     self.scroll.sr.minColumnWidth = {localization.GetByLabel('UI/Common/Name'): 44}
   1036                     self.scroll.UpdateTabStops()
   1037                 else:
   1038                     self.scroll.sr.minColumnWidth = {}
   1039                 self.refreshingView = 0
   1040                 if self.reRefreshView:
   1041                     self.reRefreshView = False
   1042                     self.RefreshCols()
   1043                     uthread.new(self.RefreshView)
   1044                 self._UpdateScrollPadding()
   1045 
   1046     def _UpdateScrollPadding(self):
   1047         if self.viewMode not in SCROLL_VIEW_MODES:
   1048             self.scroll.innerPadding = (8, 4, 0, 0)
   1049             self.scroll.padding = (-8, 0, 0, 0)
   1050             self.scroll.sr.maincontainer.padTop = -4
   1051             self.scroll.sr.clipper.clipChildren = False
   1052         else:
   1053             self.scroll.innerPadding = 0
   1054             self.scroll.padding = 0
   1055             self.scroll.sr.maincontainer.padTop = 0
   1056             self.scroll.sr.clipper.clipChildren = True
   1057 
   1058     def SelectAll(self):
   1059         if not self.destroyed:
   1060             self.scroll.SelectAll()
   1061 
   1062     def InvertSelection(self):
   1063         if not self.destroyed:
   1064             self.scroll.ToggleSelected()
   1065 
   1066     def GetIcons(self):
   1067         return [ icon for row in self.scroll.GetContentContainer().children for icon in row.sr.icons if icon.display ]
   1068 
   1069     def OnScrollMouseDown(self, *args):
   1070         if args[0] == uiconst.MOUSELEFT:
   1071             self.dragStart = self.GetDragPosition()
   1072             if uicore.uilib.Key(uiconst.VK_CONTROL) or uicore.uilib.Key(uiconst.VK_SHIFT):
   1073                 self.previouslyHighlighted = [ x.panel for x in self.scroll.GetSelected() ]
   1074             else:
   1075                 self.previouslyHighlighted = []
   1076             dragContainer = getattr(self, 'dragContainer', None)
   1077             if dragContainer is None or dragContainer.destroyed:
   1078                 self.dragContainer = Container(name='dragContainer', parent=self.GetRubberbandParentContainer(), align=uiconst.TOPLEFT, idx=0)
   1079                 FrameThemeColored(parent=self.dragContainer, frameConst=uiconst.FRAME_BORDER1_CORNER3, colorType=uiconst.COLORTYPE_UIHILIGHT, opacity=0.5)
   1080                 FillThemeColored(parent=self.dragContainer, frameConst=uiconst.FRAME_FILLED_CORNER3, opacity=0.3)
   1081             self.dragContainer.Hide()
   1082             uthread.new(self.RubberbandSelection_thread)
   1083 
   1084     def IsRowInDragArea(self, row_top, row_height, startY, currentY):
   1085         isYBeforeRow = lambda y: y < row_top
   1086         isYAfterRow = lambda y: row_top + row_height < y
   1087         isSelectionBeforeRow = lambda startY, currentY: isYBeforeRow(startY) and isYBeforeRow(currentY)
   1088         isSelectionAfterRow = lambda startY, currentY: isYAfterRow(startY) and isYAfterRow(currentY)
   1089         return not (isSelectionBeforeRow(startY, currentY) or isSelectionAfterRow(startY, currentY))
   1090 
   1091     def IsIconInDragArea(self, icon, startX, currentX):
   1092         iconX = self._CorrectXInScroll(icon.absoluteLeft)
   1093         isXBeforeIcon = lambda x: x < iconX
   1094         isXAfterIcon = lambda x: iconX + icon.width < x
   1095         isSelectionBeforeIcon = lambda startX, currentX: isXBeforeIcon(startX) and isXBeforeIcon(currentX)
   1096         isSelectionAfterIcon = lambda startX, currentX: isXAfterIcon(startX) and isXAfterIcon(currentX)
   1097         return not (isSelectionBeforeIcon(startX, currentX) or isSelectionAfterIcon(startX, currentX))
   1098 
   1099     def _CorrectXInScroll(self, x):
   1100         return x - self.scroll.GetContentContainer().absoluteLeft
   1101 
   1102     def _CorrectYInScroll(self, y):
   1103         return y - self.scroll.GetContentContainer().absoluteTop
   1104 
   1105     def GetDragPosition(self):
   1106         x = self._CorrectXInScroll(uicore.uilib.x)
   1107         y = self._CorrectYInScroll(uicore.uilib.y)
   1108         return (x, y)
   1109 
   1110     def GetRubberbandParentContainer(self):
   1111         return self.scroll.GetContentContainer()
   1112 
   1113     def UpdateDragContainerPosition(self, startX, startY, currentX, currentY):
   1114         xCorrection = 0
   1115         yCorrection = 0
   1116         scrollContent = self.scroll.GetContentContainer()
   1117         dragArea = self.GetRubberbandParentContainer()
   1118         if scrollContent != dragArea:
   1119             xCorrection = scrollContent.absoluteLeft - dragArea.absoluteLeft
   1120             yCorrection = scrollContent.absoluteTop - dragArea.absoluteTop
   1121         left = max(startX + xCorrection, 0)
   1122         width = min(currentX + xCorrection, self.GetRubberbandParentContainer().displayWidth) - left
   1123         top = max(startY + yCorrection, 0)
   1124         height = min(currentY + yCorrection, self.GetRubberbandParentContainer().displayHeight) - top
   1125         self.dragContainer.left = left
   1126         self.dragContainer.top = top
   1127         self.dragContainer.width = width
   1128         self.dragContainer.height = height
   1129 
   1130     def RubberbandSelection_thread(self, *args):
   1131         from eve.client.script.ui.shared.item import Item
   1132         timer = IntervalTimer()
   1133         scroll_remainder = 0.0
   1134         block_scrolling = not is_mouse_inside_vertically(self.scroll)
   1135         while self.dragStart and uicore.uilib.leftbtn and trinity.app.IsActive():
   1136             startX, startY = self.dragStart
   1137             currentX, currentY = self.GetDragPosition()
   1138             if startX > currentX:
   1139                 temp = currentX
   1140                 currentX = startX
   1141                 startX = temp
   1142             if startY > currentY:
   1143                 temp = currentY
   1144                 currentY = startY
   1145                 startY = temp
   1146             self.UpdateDragContainerPosition(startX, startY, currentX, currentY)
   1147             self.dragContainer.Show()
   1148             for each in self.scroll.GetContentContainer().children:
   1149                 if isinstance(each, Row):
   1150                     row_top = each.top
   1151                     row_height = self.itemClass.GetEntryHeight()
   1152                     if self.IsRowInDragArea(row_top, row_height, startY, currentY):
   1153                         for icon in each.sr.icons:
   1154                             if self.IsIconInDragArea(icon, startX, currentX):
   1155                                 if icon in self.previouslyHighlighted:
   1156                                     if uicore.uilib.Key(uiconst.VK_SHIFT):
   1157                                         icon.Select()
   1158                                     elif uicore.uilib.Key(uiconst.VK_CONTROL):
   1159                                         icon.Deselect()
   1160                                 else:
   1161                                     icon.Select()
   1162                             elif icon not in self.previouslyHighlighted:
   1163                                 icon.Deselect()
   1164                             else:
   1165                                 icon.Select()
   1166 
   1167                     else:
   1168                         for icon in each.sr.icons:
   1169                             if icon not in self.previouslyHighlighted:
   1170                                 icon.Deselect()
   1171                             else:
   1172                                 icon.Select()
   1173 
   1174                 elif isinstance(each, Item):
   1175                     if each.top >= startY and each.top + each.height <= currentY or each.top + each.height >= startY and each.top <= currentY:
   1176                         each.Select()
   1177                         if each in self.previouslyHighlighted:
   1178                             if uicore.uilib.Key(uiconst.VK_SHIFT):
   1179                                 each.Select()
   1180                             elif uicore.uilib.Key(uiconst.VK_CONTROL):
   1181                                 each.Deselect()
   1182                         else:
   1183                             each.Select()
   1184                     elif each not in self.previouslyHighlighted:
   1185                         each.Deselect()
   1186                     else:
   1187                         each.Select()
   1188 
   1189             dt = timer.tick().total_seconds()
   1190             if block_scrolling:
   1191                 block_scrolling = not is_mouse_inside_vertically(self.scroll)
   1192             if not block_scrolling:
   1193                 scroll_speed = calculate_drag_scroll_speed_vertical(self.scroll)
   1194                 if not mathext.is_almost_zero(scroll_speed):
   1195                     scroll_delta = scroll_speed * dt
   1196                     scroll_remainder += scroll_delta % 1.0
   1197                     while scroll_remainder >= 1.0:
   1198                         scroll_delta += 1.0
   1199                         scroll_remainder -= 1.0
   1200 
   1201                     self.scroll.ScrollPixels(mathext.floor(scroll_delta))
   1202             eveui.wait_for_next_frame()
   1203 
   1204         self.dragStart = None
   1205         self.previouslyHighlighted = None
   1206         self.dragContainer.Hide()
   1207 
   1208     def OnScrollMouseUp(self, *args):
   1209         from eve.client.script.ui.shared.item import Item
   1210         if self.dragStart and args[0] == uiconst.MOUSELEFT:
   1211             startX, startY = self.dragStart
   1212             endX, endY = self.GetDragPosition()
   1213             if startX > endX:
   1214                 temp = endX
   1215                 endX = startX
   1216                 startX = temp
   1217             if startY > endY:
   1218                 temp = endY
   1219                 endY = startY
   1220                 startY = temp
   1221             preSelectedNodes = self.scroll.GetSelected() if uicore.uilib.Key(uiconst.VK_CONTROL) or uicore.uilib.Key(uiconst.VK_SHIFT) else []
   1222             selectedNodes = []
   1223             for each in self.scroll.GetContentContainer().children:
   1224                 if isinstance(each, Row):
   1225                     row_top = each.top
   1226                     row_height = self.itemClass.GetEntryHeight()
   1227                     if self.IsRowInDragArea(row_top, row_height, startY, endY):
   1228                         for icon in each.sr.icons:
   1229                             if self.IsIconInDragArea(icon, startX, endX) and icon.sr.node:
   1230                                 selectedNodes.append(icon.sr.node)
   1231 
   1232                 elif isinstance(each, Item):
   1233                     if each.top >= startY and each.top + each.height <= endY or each.top + each.height >= startY and each.top <= endY:
   1234                         selectedNodes.append(each.sr.node)
   1235 
   1236             if uicore.uilib.Key(uiconst.VK_SHIFT):
   1237                 selectedNodes.extend(preSelectedNodes)
   1238             elif uicore.uilib.Key(uiconst.VK_CONTROL):
   1239                 newSelectedNodes = [ item for item in selectedNodes if item not in preSelectedNodes ]
   1240                 newSelectedNodes.extend([ item for item in preSelectedNodes if item not in selectedNodes ])
   1241                 selectedNodes = newSelectedNodes
   1242             uthread.new(self.scroll.SelectNodes, selectedNodes)
   1243             self.dragStart = None
   1244             self.previouslyHighlighted = None
   1245             self.dragContainer.Hide()
   1246 
   1247     def GetIcon(self, index):
   1248         for each in self.scroll.GetContentContainer().children:
   1249             if getattr(each, 'index', -1) == index // self.cols * self.cols:
   1250                 lg.Info('vcont', 'GetIcon(', index, ') returns', each.sr.icons[index % self.cols].name)
   1251                 return each.sr.icons[index % self.cols]
   1252 
   1253         lg.Info('vcont', 'GetIcon(', index, ') found nothing')
   1254 
   1255     def RegisterSpecialActionButton(self, button):
   1256         pass
   1257 
   1258     def GetContainerMenu(self):
   1259         return GetContainerMenu(self)
   1260 
   1261     def IsItemPresent(self, itemID):
   1262         return itemID in [ item.itemID for item in self.items if item is not None ]
   1263 
   1264     def GetViewModeName(self, viewMode):
   1265         if viewMode == VIEWMODE_ICONS:
   1266             return localization.GetByLabel('UI/Inventory/Icons')
   1267         if viewMode == VIEWMODE_DETAILS:
   1268             return localization.GetByLabel('UI/Inventory/Details')
   1269         if viewMode == VIEWMODE_LIST:
   1270             return localization.GetByLabel('UI/Inventory/List')
   1271         if viewMode == VIEWMODE_CARDS:
   1272             return localization.GetByLabel('UI/Inventory/CardsViewMode')
   1273         return localization.GetByLabel('UI/Generic/Unknown')
   1274 
   1275     def GetActiveViewModeName(self):
   1276         return self.GetViewModeName(self.viewMode)
   1277 
   1278     def GetViewModeIcon(self, viewMode):
   1279         if viewMode == VIEWMODE_ICONS:
   1280             return eveicon.grid_view
   1281         if viewMode == VIEWMODE_DETAILS:
   1282             return eveicon.details_view
   1283         if viewMode == VIEWMODE_LIST:
   1284             return eveicon.list_view
   1285         if viewMode == VIEWMODE_CARDS:
   1286             return eveicon.card_view
   1287 
   1288     def GetActiveViewModeIcon(self):
   1289         return self.GetViewModeIcon(self.viewMode)
   1290 
   1291     def ShouldShowSortBy(self):
   1292         return self.viewMode not in SCROLL_VIEW_MODES
   1293 
   1294 
   1295 def is_mouse_inside_vertically(element):
   1296     _, element_top, _, element_height = element.GetAbsolute()
   1297     return element_top <= uicore.uilib.y <= element_top + element_height
   1298 
   1299 
   1300 class IntervalTimer(object):
   1301 
   1302     def __init__(self, now_func = None):
   1303         if now_func is None:
   1304             now_func = gametime.now
   1305         self._now_func = now_func
   1306         self._last_update = None
   1307 
   1308     def start(self):
   1309         self._last_update = self._now_func()
   1310 
   1311     def tick(self):
   1312         if self._last_update is None:
   1313             self.start()
   1314         now = self._now_func()
   1315         dt = now - self._last_update
   1316         self._last_update = now
   1317         return dt
   1318 
   1319 
   1320 def calculate_drag_scroll_speed_vertical(scroll):
   1321     MIN_SCROLL_SPEED = 100
   1322     MAX_SCROLL_SPEED = 4000
   1323     SCROLL_SPEED_DISTANCE_FACTOR = 1.6
   1324     _, scroll_top, _, scroll_height = scroll.GetAbsolute()
   1325     distance_from_top = scroll_top - uicore.uilib.y
   1326     distance_from_bottom = uicore.uilib.y - (scroll_top + scroll_height)
   1327     if distance_from_top > 0:
   1328         return mathext.clamp(distance_from_top ** SCROLL_SPEED_DISTANCE_FACTOR, MIN_SCROLL_SPEED, MAX_SCROLL_SPEED)
   1329     elif distance_from_bottom > 0:
   1330         return -mathext.clamp(distance_from_bottom ** SCROLL_SPEED_DISTANCE_FACTOR, MIN_SCROLL_SPEED, MAX_SCROLL_SPEED)
   1331     else:
   1332         return 0
   1333 
   1334 
   1335 class Row(SE_BaseClassCore):
   1336     __guid__ = 'listentry.VirtualContainerRow'
   1337     default_showHilite = False
   1338 
   1339     def Startup(self, dad):
   1340         self.dad = dad.dad
   1341         self.initialized = False
   1342         self.sr.icons = []
   1343 
   1344     @telemetry.ZONE_METHOD
   1345     def Load(self, node):
   1346         if self.initialized:
   1347             return
   1348         self.initialized = True
   1349         self.sr.node = node
   1350         self.index = node.lenitems
   1351         for i in range(len(self.sr.icons), len(node.internalNodes)):
   1352             icon = node.itemClass(parent=self, state=uiconst.UI_NORMAL, align=uiconst.TOPLEFT)
   1353             icon.width = self.GetItemWidth(node)
   1354             icon.height = node.itemHeight
   1355             icon.top = 0
   1356             icon.left = (icon.width + self.dad.colMargin) * i
   1357             self.sr.icons.append(icon)
   1358 
   1359         for icon in self.sr.icons[self.dad.cols:]:
   1360             icon.sr.node = None
   1361             icon.subnodeIdx = None
   1362 
   1363         i = 0
   1364         for subnode in node.internalNodes:
   1365             icon = self.sr.icons[i]
   1366             if not node.itemClass.IsFixedWidth():
   1367                 icon.width = self.GetItemWidth(node)
   1368                 icon.left = (icon.width + self.dad.colMargin) * i
   1369             if subnode is None:
   1370                 icon.display = False
   1371                 icon.sr.node = None
   1372                 icon.subnodeIdx = None
   1373             else:
   1374                 subnode.panel = icon
   1375                 icon.Load(subnode)
   1376                 icon.display = True
   1377                 icon.subnodeIdx = subnode.idx = self.index + i
   1378             i += 1
   1379 
   1380     def GetItemWidth(self, node):
   1381         if node.itemClass.IsFixedWidth():
   1382             return node.itemWidth
   1383         return max(node.itemWidth, (self.width + self.dad.colMargin) / len(self.sr.node.internalNodes) - self.dad.colMargin)
   1384 
   1385     def GetMenu(self):
   1386         return self.dad.GetContainerMenu()
   1387 
   1388     def OnDropData(self, dragObj, nodes):
   1389         l, t, w, h = self.GetAbsolute()
   1390         index = self.index + (uicore.uilib.x - l) // (64 + self.dad.colMargin)
   1391         self.dad.OnDropDataWithIdx(nodes, index)
   1392 
   1393     def OnDragEnter(self, dragObj, nodes):
   1394         self.sr.node.container.OnDragEnter(dragObj, nodes)
   1395 
   1396     def OnDragExit(self, dragObj, nodes):
   1397         self.sr.node.container.OnDragExit(dragObj, nodes)
   1398 
   1399     def OnMouseDown(self, *etc):
   1400         if self.dad.scroll:
   1401             self.dad.scroll.OnMouseDown(*etc)
   1402 
   1403     def OnMouseUp(self, *etc):
   1404         if self.dad.scroll:
   1405             self.dad.scroll.OnMouseUp(*etc)
   1406 
   1407     def OnMouseMove(self, *etc):
   1408         if self.dad.scroll:
   1409             self.dad.scroll.OnMouseMove(*etc)
   1410 
   1411     def ShowSelected(self, *args):
   1412         pass
   1413 
   1414 
   1415 def GetContainerMenu(containerWindow):
   1416     menu = MenuData()
   1417     if session.role & (ROLE_GML | ROLE_WORLDMOD):
   1418         menu.AddEntry(text=MenuLabel('UI/Commands/Refresh'), func=containerWindow.Refresh)
   1419         menu.AddSeparator()
   1420     menu.AddEntry(text=MenuLabel('UI/Common/SelectAll'), func=containerWindow.SelectAll)
   1421     menu.AddEntry(text=MenuLabel('UI/Inventory/InvertSelection'), func=containerWindow.InvertSelection)
   1422     if containerWindow.ShouldShowSortBy():
   1423         menu.AddSeparator()
   1424         sort_options = GetBasicSortOptions()
   1425         sort_options += [None, ('UI/Inventory/EstPrice', ('estPrice', 0)), ('UI/Inventory/EstPriceReversed', ('estPrice', 1))]
   1426         menu.AddEntry(text=MenuLabel('UI/Common/SortBy'), texturePath=eveicon.bars_sort_ascending, subMenuData=GetSortMenuForOptionsOptions(containerWindow, sort_options))
   1427     if not containerWindow.invController.viewOnly:
   1428         container_item = containerWindow.invController.GetInventoryItem()
   1429         container_slim = sm.GetService('michelle').GetItem(container_item.itemID)
   1430         offer_stack_all = should_offer_stack_all(session, container_group_id=container_item.groupID, container_category_id=container_item.categoryID, container_owner_id=container_item.ownerID, container_corporation_id=getattr(container_slim, 'corpID', None), container_alliance_id=getattr(container_slim, 'allianceID', None))
   1431         if offer_stack_all:
   1432             menu.AddSeparator()
   1433             menu.AddEntry(text=MenuLabel('UI/Inventory/StackAll'), texturePath=eveicon.stack, func=containerWindow.StackAll)
   1434     return menu
   1435 
   1436 
   1437 def should_offer_stack_all(session, container_group_id, container_category_id, container_owner_id, container_corporation_id, container_alliance_id):
   1438     return container_group_id in (const.groupStation, const.groupPlanetaryCustomsOffices) or container_category_id == const.categoryStructure or container_owner_id in (session.charid, session.corpid, session.allianceid) or session.corpid == container_corporation_id or session.allianceid and session.allianceid == container_alliance_id
   1439 
   1440 
   1441 def GetSortMenuForOptionsOptions(containerWindow, options):
   1442     m = []
   1443     for eachOption in options:
   1444         if eachOption is None:
   1445             m.append(None)
   1446         else:
   1447             labelPath, args = eachOption
   1448             m.append((MenuLabel(labelPath), containerWindow.SortIconsBy, args))
   1449 
   1450     return m
   1451 
   1452 
   1453 def GetBasicSortOptions():
   1454     return [('UI/Common/Name', ('name', 0)),
   1455      ('UI/Inventory/NameReversed', ('name', 1)),
   1456      None,
   1457      ('UI/Inventory/ItemQuantity', ('qty', 0)),
   1458      ('UI/Inventory/QuantityReversed', ('qty', 1)),
   1459      None,
   1460      ('UI/Common/Type', ('type', 0)),
   1461      ('UI/Inventory/TypeReversed', ('type', 1))]