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

overviewWindow.py (59317B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\inflight\overview\overviewWindow.py
      2 import _weakref
      3 import appConst
      4 import bisect
      5 import logging
      6 import sys
      7 from collections import defaultdict
      8 import blue
      9 import stackless
     10 import telemetry
     11 import eveicon
     12 import evetypes
     13 import gametime
     14 import homestation.client
     15 import localization
     16 import locks
     17 import overviewPresets.overviewSettingsConst as osConst
     18 import trinity
     19 import uthread
     20 import uthread2
     21 from carbon.client.script.environment.AudioUtil import PlaySound
     22 from carbon.common.script.sys.serviceConst import ROLE_GML
     23 from carbon.common.script.util.format import FmtDist
     24 from carbonui.control.basicDynamicScroll import BasicDynamicScroll
     25 from carbonui.control.buttonIcon import ButtonIcon
     26 from carbonui.control.contextMenu.menuEntryData import MenuEntryData, MenuEntryDataCheckbox, MenuEntryDataCaption
     27 from carbonui.decorative.divider_line import DividerLine
     28 from carbonui.uicore import uicore
     29 from carbonui.util.color import Color
     30 from carbonui.util.various_unsorted import IsUnder
     31 from carbonui.util import colorblind
     32 from carbonui.window.header.small import SmallWindowHeader
     33 from carbonui.window.widget import WidgetWindow
     34 from eve.client.script.parklife import states as state
     35 from eve.client.script.parklife.overview import overviewSignals
     36 from eve.client.script.ui.control.entries.util import GetFromClass
     37 from carbonui.window.control.action import WindowMenuAction
     38 from eve.client.script.ui.inflight import actionPanelDefaults
     39 from eve.client.script.ui.inflight.actions import ActionPanel
     40 from eve.client.script.ui.inflight.bracketsAndTargets.bracketVarious import GetIconColor
     41 from eve.client.script.ui.inflight.overViewLabel import OverviewLabel, SortHeaders
     42 from eve.client.script.ui.inflight.overview import overviewConst, overviewColumns, overviewSettings, overviewPulse
     43 from eve.client.script.ui.inflight.overview.addTabPopup import AddTabPopup
     44 from eve.client.script.ui.inflight.overview.overviewConst import *
     45 from eve.client.script.ui.inflight.overview.overviewMenuUtil import GetBracketVisibilitySubMenu, GetColumnVisibilitySubMenu
     46 from eve.client.script.ui.inflight.overview.overviewNodeUtil import PrimeDisplayName, UpdateIconAndBackgroundFlagsOnNode, PrimeCorpAndAllianceName, UpdateVelocityData
     47 from eve.client.script.ui.inflight.overview.overviewScrollEntry import OverviewScrollEntry
     48 from eve.client.script.ui.inflight.overview.overviewTabGroup import OverviewTabGroup
     49 from eve.client.script.ui.inflight.overview.overviewUtil import GetSortValueWhenBroadcastGoToTop, GetAllianceTickerName, GetCorpTickerName, PrepareLocalizationTooltip, GetSlimItemForCharID, GetEntryFontSize, GetEntryHeight, GetColumnValuesToCalculate, GetColumnSettingsAndSortKeys
     50 from eve.client.script.ui.inflight.overview.slimItemDisplayChecker import SlimItemDisplayChecker
     51 from eve.client.script.ui.shared.pointerTool import pointerToolConst as pConst
     52 from eve.client.script.ui.shared.stateFlag import GetIconFlagAndBackgroundFlag, FindDockingRightsToDisplay, GetGateLockValue, GetLockedToGateID, GetGateLockValueForSlimItem
     53 from eve.common.lib import appConst as const
     54 from eve.common.script.sys.idCheckers import IsSolarSystem
     55 from eveexceptions import ExceptionEater
     56 from globalConfig.getFunctions import GetOverviewMousemovementTimeout
     57 from inventorycommon.util import IsNPC
     58 from menu import MenuLabel
     59 from npcs.npccorporations import get_corporation_faction_id
     60 from overviewPresets.overviewSettingsConst import SETTING_TAB_NAME, SETTING_TAB_COLOR, MAX_TAB_NUM
     61 from spacecomponents.common.helper import GetMineableComponentTypeAttributes, HasMineableComponent
     62 from stargate.client.gate_signals import on_lock_removed
     63 from structures import STATE_UNANCHORED, DOCKING_ACCESS_UNKNOWN
     64 from structures.types import IsFlexStructure
     65 from uihider import UiHiderMixin
     66 ScrollListLock = locks.RLock()
     67 log = logging.getLogger(__name__)
     68 
     69 class OverviewWindow(UiHiderMixin, WidgetWindow):
     70     __notifyevents__ = ['OnDestinationSet',
     71      'OnOverviewPresetsChanged',
     72      'OnReloadingOverviewProfile',
     73      'OnEwarStart',
     74      'OnEwarEnd',
     75      'OnStateSetupChange',
     76      'OnSessionChanged',
     77      'OnFleetJoin_Local',
     78      'OnFleetLeave_Local',
     79      'OnPostCfgDataChanged',
     80      'OnOverviewPresetLoaded',
     81      'OnFleetStateChange',
     82      'OnStateChange',
     83      'DoBallsAdded',
     84      'DoBallRemove',
     85      'OnSlimItemChange',
     86      'OnContactChange',
     87      'OnItemHighlighted',
     88      'ProcessBountyInfoUpdated',
     89      'DoBallsRemove',
     90      'OnStructuresVisibilityUpdated',
     91      'OnEnterSpace',
     92      'OnRefreshWhenDockingRequestDenied',
     93      'OnStructureAccessUpdated',
     94      'OnOverviewOverrideChangedOnDoBallAddEgoChange',
     95      'OnShipLabelsUpdated',
     96      'OnFullOverviewReload',
     97      'OnFreezeOverviewWindows',
     98      'OnUnfreezeOverviewWindows',
     99      'OnFlagsInvalidated',
    100      'OnOverviewTabsChanged',
    101      'OnBracketIconUpdated',
    102      'OnEmanationLockUpdated']
    103     default_windowID = overviewConst.WINDOW_ID
    104     default_open = True
    105     default_cursor = uiconst.UICURSOR_HASMENU
    106     default_isKillable = False
    107     default_scope = uiconst.SCOPE_INFLIGHT
    108     default_minSize = (128, 128)
    109     default_iconNum = 'res:/ui/Texture/WindowIcons/overview.png'
    110     sortingFrozen = False
    111     uniqueUiName = pConst.UNIQUE_NAME_OVERVIEW_WND
    112     _tab_line = None
    113 
    114     @staticmethod
    115     def default_left(*args):
    116         return actionPanelDefaults.GetOverviewLeft()
    117 
    118     @staticmethod
    119     def default_top(*args):
    120         return actionPanelDefaults.GetOverviewTop()
    121 
    122     @staticmethod
    123     def default_width():
    124         return actionPanelDefaults.GetOverviewWidth()
    125 
    126     @staticmethod
    127     def default_height():
    128         return actionPanelDefaults.GetOverviewHeight()
    129 
    130     @telemetry.ZONE_METHOD
    131     def ApplyAttributes(self, attributes):
    132         self.stateSvc = sm.GetService('stateSvc')
    133         self.fleetSvc = sm.GetService('fleet')
    134         self.machoNet = sm.GetService('machoNet')
    135         self.presetSvc = sm.GetService('overviewPresetSvc')
    136         self.michelle = sm.GetService('michelle')
    137         super(OverviewWindow, self).ApplyAttributes(attributes)
    138         self.updateEntryOrderThread = None
    139         self._freezeOverview = False
    140         self._ballparkDirty = True
    141         self._scrollEntriesDirty = True
    142         self._scrollNodesByItemID = {}
    143         self.displayChecker = SlimItemDisplayChecker()
    144         self.tabGroup = None
    145         self._update_extend_content_into_header()
    146         self.jammers = {}
    147         self.ewarTypes = self.stateSvc.GetEwarTypes()
    148         self.ewarHintsByGraphicID = {}
    149         for jamType, (flag, graphicID) in self.ewarTypes:
    150             self.ewarHintsByGraphicID[graphicID] = self.stateSvc.GetEwarHint(jamType)
    151 
    152         self.prevMouseCoords = trinity.mainWindow.GetCursorPos()
    153         self.lastMovementTime = blue.os.GetWallclockTime()
    154         self.mouseMovementTimeout = GetOverviewMousemovementTimeout(self.machoNet)
    155         homeStationService = homestation.Service.instance()
    156         if not homeStationService.is_home_station_data_loaded:
    157             uthread.new(homeStationService.get_home_station)
    158         self._ConnectSignals()
    159         self.tabGroup = OverviewTabGroup(align=uiconst.TOTOP, parent=self.content, padding=self._get_tab_group_padding(), groupID='overviewTabs', UIIDPrefix='OverviewTab_', callback=self.OnTabSelected, show_line=False)
    160         self._tab_line = DividerLine(parent=self.content, align=uiconst.TOTOP_NOPUSH, top=self._get_tab_line_top(), padding=self._get_tab_line_padding())
    161         self.ConstructScroll()
    162         self.ReconstructTabs()
    163 
    164     def ConstructScroll(self):
    165         self.scroll = BasicDynamicScroll(name='overviewscroll2', align=uiconst.TOALL, parent=self.content, multiSelect=False, autoPurgeHiddenEntries=False)
    166         self.scroll.OnSelectionChange = self.OnScrollSelectionChange
    167         self.scroll.OnChar = self.OnChar
    168         self.scroll.OnKeyUp = self.OnKeyUp
    169         self.sortHeaders = SortHeaders(parent=self.scroll.sr.maincontainer, settingsID='overviewScroll2', idx=0, get_menu_func=self.GetMenuForsortHeaders)
    170         self.sortHeaders.SetDefaultColumn(COLUMN_DISTANCE, True)
    171         self.sortHeaders.OnColumnSizeChange = self.OnColumnSizeChanged
    172         self.sortHeaders.OnSortingChange = self.OnSortingChange
    173         self.sortHeaders.OnColumnSizeReset = self.OnColumnSizeReset
    174 
    175     def GetMenuForsortHeaders(self):
    176         return GetColumnVisibilitySubMenu(self.GetSelectedTabID())
    177 
    178     def GetScrollContent(self):
    179         return self.scroll.sr.content
    180 
    181     def _ConnectSignals(self):
    182         homeStationService = homestation.Service.instance()
    183         homeStationService.on_home_station_changed.connect(self._OnHomeStationChanged)
    184         homeStationService.on_home_station_data_loaded.connect(self._UpdateHomeStation)
    185         self.on_compact_mode_changed.connect(self._on_compact_mode_changed)
    186         self.on_header_inset_changed.connect(self._on_header_inset_changed)
    187         self.on_content_padding_changed.connect(self._on_content_padding_changed)
    188         overviewSignals.on_preset_saved.connect(self.OnOverviewPresetsChanged)
    189         overviewSignals.on_preset_saved_as.connect(self.OnPresetSavedAs)
    190         overviewSignals.on_preset_restored.connect(self.OnOverviewPresetsChanged)
    191         overviewSignals.on_tab_columns_changed.connect(self.OnTabColumnsChanged)
    192         overviewPulse.connect_to_visible_entry_update_pulse(self.OnVisibleEntryUpdatePulse)
    193         colorblind.on_colorblind_mode_changed.connect(self.OnColorblindModeChanged)
    194         on_lock_removed.connect(self.OnEmanationGateLockRemoved)
    195 
    196     def OnVisibleEntryUpdatePulse(self):
    197         if self.destroyed:
    198             return
    199         self.LoadVisibleEntries()
    200 
    201     def OnTabColumnsChanged(self, tabID):
    202         if tabID == self.GetSelectedTabID():
    203             self.ReconstructTabs()
    204             self.FullReload()
    205 
    206     def OnPresetSavedAs(self, oldPresetName, presetName):
    207         tabID = self.GetSelectedTabID()
    208         currTabPresetName = self.presetSvc.GetTabPreset(tabID)
    209         (oldPresetName,
    210          presetName,
    211          currTabPresetName,
    212          tabID,
    213          self.destroyed)
    214         if oldPresetName != presetName and currTabPresetName == oldPresetName:
    215             self.presetSvc.SetTabPreset(tabID, presetName)
    216         self.OnOverviewPresetsChanged()
    217 
    218     def _on_compact_mode_changed(self, window):
    219         self.header.show_caption = not self.compact
    220         self._update_extend_content_into_header()
    221         self._update_tab_line_top()
    222         self.ReconstructTabs()
    223         self.FullReload()
    224 
    225     def _on_content_padding_changed(self, window):
    226         self._update_tab_line_padding()
    227         self._update_tabs_padding()
    228 
    229     def _on_header_inset_changed(self, window):
    230         self._update_tabs_padding()
    231 
    232     def _update_extend_content_into_header(self):
    233         self.extend_content_into_header = self.compact
    234 
    235     def _get_tab_line_padding(self):
    236         pad_left, _, pad_right, _ = self.content_padding
    237         return (-pad_left,
    238          0,
    239          -pad_right,
    240          0)
    241 
    242     def _update_tab_line_padding(self):
    243         if self._tab_line:
    244             self._tab_line.padding = self._get_tab_line_padding()
    245 
    246     def _get_tab_line_top(self):
    247         _, _, _, tabs_pad_bottom = self._get_tab_group_padding()
    248         return -1 - tabs_pad_bottom
    249 
    250     def _update_tab_line_top(self):
    251         if self._tab_line:
    252             self._tab_line.top = self._get_tab_line_top()
    253 
    254     def _get_tab_group_padding(self):
    255         _, inset_right = self.header_inset
    256         _, pad_top, pad_right, _ = self.content_padding
    257         return (0,
    258          -8,
    259          inset_right - pad_right if self.compact else -4,
    260          4 if self.compact else 8)
    261 
    262     def _update_tabs_padding(self):
    263         self.tabGroup.padding = self._get_tab_group_padding()
    264 
    265     def Prepare_Header_(self):
    266         self._SetHeader(SmallWindowHeader(show_caption=not self.compact))
    267 
    268     def get_wnd_menu_unique_name(self):
    269         return pConst.UNIQUE_NAME_OVERVIEW_SETTINGS
    270 
    271     def Close(self, setClosed = False, *args, **kwds):
    272         ActionPanel.Close(self)
    273         with ExceptionEater('closing OverviewWindow'):
    274             homeStationService = homestation.Service.instance()
    275             homeStationService.on_home_station_changed.disconnect(self._OnHomeStationChanged)
    276             homeStationService.on_home_station_data_loaded.disconnect(self._UpdateHomeStation)
    277             on_lock_removed.disconnect(self.OnEmanationGateLockRemoved)
    278 
    279     @telemetry.ZONE_METHOD
    280     def DoBallsRemove(self, pythonBalls, isRelease):
    281         for ball, slimItem, terminal in pythonBalls:
    282             self.DoBallRemove(ball, slimItem, terminal)
    283 
    284     def DoBallRemove(self, ball, slimItem, terminal):
    285         if ball is None:
    286             return
    287         itemID = slimItem.itemID
    288         node = self._scrollNodesByItemID.get(itemID, None)
    289         if node:
    290             node.leavingOverview = True
    291             if node.panel:
    292                 node.panel.opacity = 0.25
    293                 node.panel.state = uiconst.UI_DISABLED
    294             if node.itemID in self._scrollNodesByItemID:
    295                 del self._scrollNodesByItemID[node.itemID]
    296 
    297     def DoBallsAdded(self, lst, *args, **kw):
    298         uthread.new(self._DoBallsAdded, lst, *args, **kw)
    299 
    300     @telemetry.ZONE_METHOD
    301     def _DoBallsAdded(self, lst, *args, **kw):
    302         columns = overviewColumns.GetColumns(self.GetSelectedTabID())
    303         if self.sortHeaders.GetCurrentColumns() != columns:
    304             self.sortHeaders.CreateColumns(columns, fixedColumns=FIXEDCOLUMNS)
    305         with ScrollListLock:
    306             newEntries = []
    307             for ball, slimItem in lst:
    308                 if slimItem.itemID in self._scrollNodesByItemID:
    309                     continue
    310                 if not self.displayChecker.ShouldDisplayItem(slimItem):
    311                     continue
    312                 updateItem = self.stateSvc.CheckIfUpdateItem(slimItem)
    313                 newNode = GetFromClass(OverviewScrollEntry, {'itemID': slimItem.itemID,
    314                  'updateItem': updateItem})
    315                 newNode.ball = _weakref.ref(ball)
    316                 newNode.slimItem = _weakref.ref(slimItem)
    317                 if updateItem:
    318                     newNode.ewarGraphicIDs = self.GetEwarDataForNode(newNode)
    319                 newNode.ewarHints = self.ewarHintsByGraphicID
    320                 newEntries.append(newNode)
    321 
    322             if newEntries:
    323                 self._UpdateStaticDataForNodes(newEntries)
    324                 self._UpdateDynamicDataForNodes(newEntries)
    325                 currentActive, currentDirection = self.sortHeaders.GetCurrentActive()
    326                 broadcastsToTop = self.presetSvc.GetSettingValueBroadcastToTop()
    327                 fleetBroadcasts = self.fleetSvc.GetCurrentFleetBroadcasts()
    328 
    329                 def GetSortValue(_node):
    330                     if broadcastsToTop:
    331                         return GetSortValueWhenBroadcastGoToTop(_node, fleetBroadcasts, currentDirection)
    332                     return _node.sortValue
    333 
    334                 self.scroll.ShowHint()
    335                 if self.sortingFrozen:
    336                     newEntries.sort(key=lambda x: GetSortValue(x), reverse=not currentDirection)
    337                     self.scroll.AddNodes(-1, newEntries)
    338                 else:
    339                     sortValues = [ GetSortValue(x) for x in self.scroll.sr.nodes ]
    340                     entriesAtIdx = defaultdict(list)
    341                     for entry in newEntries:
    342                         insertionIndex = bisect.bisect(sortValues, GetSortValue(entry))
    343                         entriesAtIdx[insertionIndex].append(entry)
    344 
    345                     insertionPoints = sorted(entriesAtIdx.keys(), reverse=True)
    346                     for insertionIdx in insertionPoints:
    347                         sortedGroup = sorted(entriesAtIdx[insertionIdx], key=GetSortValue, reverse=not currentDirection)
    348                         self.scroll.AddNodes(insertionIdx, sortedGroup)
    349 
    350     def UpdateUIScaling(self, value, oldValue):
    351         super(OverviewWindow, self).UpdateUIScaling(value, oldValue)
    352         self.FullReload()
    353 
    354     def OnStateChange(self, itemID, flag, newState, *args):
    355         node = self._scrollNodesByItemID.get(itemID, None)
    356         if node and node.panel:
    357             node.panel.OnStateChange(itemID, flag, newState, *args)
    358         if flag in (state.flagWreckEmpty, state.flagWreckAlreadyOpened):
    359             self.FlagBallparkDirty()
    360 
    361     def OnFleetStateChange(self, fleetState):
    362         if not fleetState:
    363             return
    364         for itemID, tag in fleetState.targetTags.iteritems():
    365             node = self._scrollNodesByItemID.get(itemID, None)
    366             if node is None:
    367                 continue
    368             node.display_TAG = tag
    369             if node.sortTagIndex is not None:
    370                 if tag:
    371                     node.sortValue[node.sortTagIndex] = tag.lower()
    372                 else:
    373                     node.sortValue[node.sortTagIndex] = 0
    374 
    375     def OnSlimItemChange(self, oldSlim, newSlim):
    376         node = self._scrollNodesByItemID.get(oldSlim.itemID, None)
    377         if node:
    378             node.slimItem = _weakref.ref(newSlim)
    379             node.iconColor = None
    380             PrimeDisplayName(node)
    381             currentActive, _ = self.sortHeaders.GetCurrentActive()
    382             columnSettings, _ = GetColumnSettingsAndSortKeys(currentActive, self.GetSelectedTabID())
    383             PrimeCorpAndAllianceName(node, columnSettings)
    384             UpdateIconAndBackgroundFlagsOnNode(node)
    385             if node.panel:
    386                 node.panel.UpdateIcon()
    387 
    388     def ProcessBountyInfoUpdated(self, itemIDs):
    389         for itemID in itemIDs:
    390             node = self._scrollNodesByItemID.get(itemID, None)
    391             if node is not None:
    392                 UpdateIconAndBackgroundFlagsOnNode(node)
    393 
    394     def FlushEwarStates(self):
    395         if self.jammers:
    396             currentSourceIDs = self.jammers.keys()
    397             self.jammers = {}
    398             for sourceBallID in currentSourceIDs:
    399                 self.UpdateEwarStateOnItemID(sourceBallID)
    400 
    401     def OnEwarStart(self, sourceBallID, moduleID, targetBallID, jammingType):
    402         if targetBallID != session.shipid:
    403             return
    404         if not jammingType:
    405             return
    406         if not hasattr(self, 'jammers'):
    407             self.jammers = {}
    408         jammingID = sm.StartService('stateSvc').GetEwarGraphicID(jammingType)
    409         if sourceBallID not in self.jammers:
    410             self.jammers[sourceBallID] = {}
    411         if jammingID not in self.jammers[sourceBallID]:
    412             self.jammers[sourceBallID][jammingID] = {}
    413         self.jammers[sourceBallID][jammingID][moduleID] = True
    414         self.UpdateEwarStateOnItemID(sourceBallID)
    415 
    416     def OnEwarEnd(self, sourceBallID, moduleID, targetBallID, jammingType):
    417         if targetBallID != session.shipid:
    418             return
    419         if not jammingType:
    420             return
    421         if not hasattr(self, 'jammers'):
    422             return
    423         jammingID = sm.StartService('stateSvc').GetEwarGraphicID(jammingType)
    424         if sourceBallID not in self.jammers or jammingID not in self.jammers[sourceBallID] or moduleID not in self.jammers[sourceBallID][jammingID]:
    425             return
    426         del self.jammers[sourceBallID][jammingID][moduleID]
    427         if self.jammers[sourceBallID][jammingID] == {}:
    428             del self.jammers[sourceBallID][jammingID]
    429         self.UpdateEwarStateOnItemID(sourceBallID)
    430 
    431     def UpdateEwarStateOnItemID(self, itemID):
    432         node = self._scrollNodesByItemID.get(itemID, None)
    433         if node is None:
    434             return
    435         node.ewarGraphicIDs = self.GetEwarDataForNode(node)
    436         if node.panel:
    437             node.panel.UpdateEwar()
    438 
    439     def GetEwarDataForNode(self, node):
    440         if node.itemID not in self.jammers:
    441             return
    442         jammersOnItem = self.jammers.get(node.itemID, None)
    443         if not jammersOnItem:
    444             return
    445         ret = []
    446         for jamType, (flag, graphicID) in self.ewarTypes:
    447             if graphicID in jammersOnItem:
    448                 ret.append(graphicID)
    449 
    450         return ret
    451 
    452     def OnOverviewPresetLoaded(self, presetName, preset):
    453         self.FlagScrollEntriesAndBallparkDirty_InstantUpdate('OnOverviewPresetLoaded')
    454 
    455     def UpdateWindowCaption(self, presetName):
    456         presetDisplayName = self.presetSvc.GetPresetDisplayName(presetName)
    457         text = localization.GetByLabel('UI/Tactical/OverviewCaption', preset=presetDisplayName)
    458         if overviewSettings.show_debug_info.is_enabled():
    459             text += ' windowInstanceID=%s' % self.windowInstanceID
    460         self.SetCaption(text)
    461 
    462     def OnPostCfgDataChanged(self, what, data):
    463         if what == 'evelocations':
    464             itemID = data[0]
    465             if itemID in self._scrollNodesByItemID:
    466                 node = self._scrollNodesByItemID[itemID]
    467                 PrimeDisplayName(node)
    468 
    469     def OnDestinationSet(self, *etc):
    470         for node in self.scroll.sr.nodes:
    471             slimItem = node.slimItem()
    472             if not slimItem:
    473                 continue
    474             if slimItem.groupID not in (const.groupStargate, const.groupStation) and slimItem.categoryID != const.categoryStructure:
    475                 continue
    476             node.iconColor = None
    477 
    478     def OnContactChange(self, contactIDs, contactType = None):
    479         self.FlagBallparkDirty()
    480 
    481     def OnFleetJoin_Local(self, member, *args):
    482         self.UpdateFleetMemberOrFlagDirty(member)
    483         self.FlagBallparkDirty()
    484 
    485     def OnFleetLeave_Local(self, member, *args):
    486         self.UpdateFleetMemberOrFlagDirty(member)
    487         self.FlagBallparkDirty()
    488 
    489     def OnDoBallAddEgoChange(self):
    490         self.TriggerInstantUpdate('OnDoBallAddEgoChange')
    491 
    492     def OnShipLabelsUpdated(self):
    493         self.FullReload()
    494 
    495     def OnFullOverviewReload(self):
    496         self.FullReload()
    497 
    498     def UpdateFleetMemberOrFlagDirty(self, member):
    499         if member.charID == session.charid:
    500             self.FlagScrollEntriesDirty('UpdateFleetMemberOrFlagDirty')
    501         else:
    502             slimItem = GetSlimItemForCharID(member.charID)
    503             if slimItem and slimItem.itemID in self._scrollNodesByItemID:
    504                 node = self._scrollNodesByItemID[slimItem.itemID]
    505                 UpdateIconAndBackgroundFlagsOnNode(node)
    506 
    507     def _OnHomeStationChanged(self):
    508         homeStationService = homestation.Service.instance()
    509         if not homeStationService.is_home_station_data_loaded:
    510             uthread.new(homeStationService.get_home_station)
    511 
    512     def _UpdateHomeStation(self):
    513         homeStation = homestation.Service.instance().get_home_station()
    514         for node in self.scroll.sr.nodes:
    515             isHomeStation = node.itemID == homeStation.id
    516             if isHomeStation != node.isHomeStation:
    517                 node.isHomeStation = isHomeStation
    518                 if node.panel and node.panel.state != uiconst.UI_HIDDEN:
    519                     node.panel.Load(node)
    520                     UpdateIconAndBackgroundFlagsOnNode(node)
    521 
    522     def OnRefreshWhenDockingRequestDenied(self, structureID):
    523         self._RefreshStructureDockingAccess(structureID)
    524 
    525     def OnStructureAccessUpdated(self):
    526         structuresInOverview = {node.itemID for node in self.scroll.sr.nodes if node.slimItem and node.slimItem().categoryID == const.categoryStructure}
    527         for eachStructureID in structuresInOverview:
    528             self._RefreshStructureDockingAccess(eachStructureID)
    529 
    530     def _RefreshStructureDockingAccess(self, structureID):
    531         ballpark = self.michelle.GetBallpark()
    532         if not ballpark:
    533             return
    534         slimItem = ballpark.GetInvItem(structureID)
    535         if not slimItem:
    536             return
    537         if IsFlexStructure(slimItem.typeID):
    538             return
    539         node = self._scrollNodesByItemID.get(slimItem.itemID, None)
    540         if node:
    541             hasDockingRights = FindDockingRightsToDisplay(sm.GetService('structureProximityTracker'), slimItem)
    542             node.hasDockingRights = hasDockingRights
    543             if node.panel and node.panel.state != uiconst.UI_HIDDEN:
    544                 node.panel.Load(node)
    545                 node.panel.UpdateIcon()
    546 
    547     def OnFlagsInvalidated(self, itemIDs = None):
    548         if itemIDs:
    549             self.UpdateAllIconAndBackgroundFlagsForItemIDs(itemIDs)
    550         else:
    551             self.UpdateAllIconAndBackgroundFlagsForAllNodes()
    552 
    553     @telemetry.ZONE_METHOD
    554     def UpdateAllIconAndBackgroundFlagsForAllNodes(self):
    555         for node in self.scroll.sr.nodes:
    556             self.UpdateAllIconAndBackgroundFlagsForNode(node)
    557 
    558     @telemetry.ZONE_METHOD
    559     def UpdateAllIconAndBackgroundFlagsForItemIDs(self, itemIDs):
    560         for eachItemID in itemIDs:
    561             node = self._scrollNodesByItemID.get(eachItemID, None)
    562             if node:
    563                 self.UpdateAllIconAndBackgroundFlagsForNode(node)
    564 
    565     def UpdateAllIconAndBackgroundFlagsForNode(self, node):
    566         if node.updateItem:
    567             UpdateIconAndBackgroundFlagsOnNode(node)
    568         else:
    569             slimItem = node.slimItem()
    570             if slimItem is not None and slimItem.groupID in const.containerGroupIDs:
    571                 node.iconColor = None
    572                 if node.panel is not None:
    573                     node.panel.UpdateIconColor()
    574 
    575     def OnReloadingOverviewProfile(self):
    576         self.FullReload()
    577 
    578     def OnOverviewPresetsChanged(self, *args):
    579         self.ReconstructTabs()
    580 
    581     def OnOverviewTabsChanged(self):
    582         self.ReconstructTabs()
    583 
    584     def OnColorblindModeChanged(self):
    585         self.ReconstructTabs()
    586 
    587     def OnBracketIconUpdated(self, itemID):
    588         node = self._scrollNodesByItemID.get(itemID, None)
    589         if node and node.panel:
    590             node.panel.UpdateIcon()
    591 
    592     def ReconstructTabs(self):
    593         if self.destroyed:
    594             return
    595         selectedID = self.tabGroup.GetSelectedID()
    596         tabIDs = self.presetSvc.GetTabIDs(self.windowInstanceID)
    597         if tabIDs is None:
    598             self.Close()
    599             return
    600         self.tabGroup.Flush()
    601         self._ConstructTabs(tabIDs)
    602         self._update_tabs_padding()
    603         self._update_window_controls()
    604         numTabs = len(self.presetSvc.GetSettingsByTabID())
    605         if not self.presetSvc.IsReadOnly():
    606             if numTabs < MAX_TAB_NUM and not self.compact:
    607                 self.tabGroup.AddButton(icon=eveicon.add, func=self.AddTab, hint=localization.GetByLabel('UI/Overview/AddTab'))
    608         if selectedID is not None and selectedID in tabIDs:
    609             self.tabGroup.SelectByID(selectedID)
    610         else:
    611             self.tabGroup.AutoSelect()
    612         sm.ScatterEvent('OnOverviewTabsReconstructed')
    613 
    614     def _ShouldShowBracketDictatingIconButton(self):
    615         return self.presetSvc.IsBracketFilterDictatingWindow(self.windowInstanceID) and len(self.presetSvc.GetWindowInstanceIDs()) > 1
    616 
    617     def _ConstructTabs(self, tabIDs):
    618         for tabID in tabIDs:
    619             tabSettings = self.presetSvc.GetTabSettings(tabID)
    620             if not tabSettings:
    621                 log.warning('No settings found for tabID=%s' % tabID)
    622                 continue
    623             tabName = tabSettings.get(SETTING_TAB_NAME, None).strip()
    624             tabColor = tabSettings.get(SETTING_TAB_COLOR, None)
    625             if tabColor:
    626                 hexColor = Color.RGBtoHex(*tabColor)
    627                 tabName = '<color=%s>%s</color>' % (hexColor, tabName)
    628             self.tabGroup.AddTab(tabName, tabID=tabID)
    629 
    630     def GetCustomHeaderButtons(self):
    631         actions = super(OverviewWindow, self).GetCustomHeaderButtons()
    632         if not self.presetSvc.IsReadOnly() and self._ShouldShowBracketDictatingIconButton():
    633             actions.append(WindowMenuAction(window=self, label=localization.GetByLabel('UI/Overview/DictateBracketFilterHint'), icon=eveicon.brackets, callback=self.BracketButtonMenuCallback))
    634         return actions
    635 
    636     def BracketButtonMenuCallback(self, window):
    637         return [MenuEntryDataCaption(MenuLabel('UI/Overview/BracketVisibility'))] + GetBracketVisibilitySubMenu()
    638 
    639     def OnStateSetupChange(self, reason = None):
    640         self.FlagScrollEntriesDirty('OnStateSetupChange')
    641 
    642     def AddTab(self):
    643         PlaySound(uiconst.SOUND_BUTTON_CLICK)
    644         addTabPopup = AddTabPopup.Open()
    645         if addTabPopup.ShowModal() != 1:
    646             return
    647         name, overviewPreset, bracketPreset, inNewWindow = addTabPopup.result
    648         wndInstanceID = None if inNewWindow else self.windowInstanceID
    649         self.presetSvc.AddTab(name, overviewPreset, bracketPreset, wndInstanceID)
    650 
    651     def _SetOpacity(self, value):
    652         if self.sr.underlay:
    653             display = True if value else False
    654             self.sr.underlay.display = display
    655         super(OverviewWindow, self)._SetOpacity(value)
    656 
    657     def OnSetActive_(self, *args):
    658         if self.scroll is not None:
    659             selected = self.scroll.GetSelected()
    660             if selected is None:
    661                 self.scroll.SetSelected(0)
    662 
    663     def OnKeyUp(self, *args):
    664         selected = self.scroll.GetSelected()
    665         if not selected:
    666             return
    667         uicore.cmd.ExecuteCombatCommand(selected[0].itemID, uiconst.UI_KEYUP)
    668 
    669     def OnChar(self, *args):
    670         return False
    671 
    672     def OnTabSelected(self, tabID, oldTabID):
    673         self.presetSvc.LoadTab(tabID)
    674         self.displayChecker.UpdateState(tabID)
    675         self.sortHeaders.SetSubSettingID(tabID)
    676         presetName = self.presetSvc.GetTabPreset(tabID)
    677         self.UpdateWindowCaption(presetName)
    678 
    679     def OnColumnSizeReset(self, columnID):
    680         fontSize = GetEntryFontSize()
    681         labelClass = OverviewLabel
    682         widths = [COLUMNMINSIZE - COLUMNMARGIN * 2]
    683         for node in self.scroll.sr.nodes:
    684             displayValue = OverviewScrollEntry.GetColumnDisplayValue(node, columnID)
    685             if displayValue:
    686                 textWidth, textHeight = labelClass.MeasureTextSize(displayValue, fontSize=fontSize)
    687                 widths.append(textWidth)
    688 
    689         self.sortHeaders.SetColumnSize(columnID, max(widths) + COLUMNMARGIN * 2)
    690         self.TriggerInstantUpdate('OnColumnSizeReset')
    691 
    692     def OnColumnSizeChanged(self, columnID, headerWidth, currentSizes, *args):
    693         self.TriggerInstantUpdate('OnColumnSizeChanged')
    694 
    695     def OnSortingChange(self, oldColumnID, columnID, oldSortDirection, sortDirection):
    696         if oldColumnID == columnID and oldSortDirection != sortDirection:
    697             self.TriggerInstantUpdate('OnSortingChange')
    698         else:
    699             self.FlagScrollEntriesDirty_InstantUpdate('OnSortingChange')
    700 
    701     def OnScrollSelectionChange(self, nodes, *args):
    702         if not nodes:
    703             return
    704         node = nodes[0]
    705         if node and node.itemID:
    706             self.stateSvc.SetState(node.itemID, state.selected, 1)
    707             if sm.GetService('target').IsTarget(node.itemID):
    708                 self.stateSvc.SetState(node.itemID, state.activeTarget, 1)
    709 
    710     def GetMenuMoreOptions(self):
    711         m = []
    712         if session.role & ROLE_GML:
    713             m.append(MenuEntryData('GM / WM Extras', subMenuData=self.GetDevMenu))
    714         if self.presetSvc.IsReadOnly():
    715             return m
    716         m.append(MenuEntryData(MenuLabel('UI/Overview/AddTab'), self.AddTab, texturePath=eveicon.add.resolve(16)))
    717         m.append(MenuEntryData(MenuLabel('UI/Commands/OpenOverviewSettings'), sm.GetService('tactical').OpenSettings, texturePath=eveicon.settings))
    718         if len(self.presetSvc.GetWindowInstanceIDs()) == 1:
    719             m.append(MenuEntryData(MenuLabel('UI/Overview/BracketVisibility'), subMenuData=GetBracketVisibilitySubMenu()))
    720         return super(OverviewWindow, self).GetMenuMoreOptions() + m
    721 
    722     def GetDevMenu(self):
    723         m = self.presetSvc.GetPresetsMenuElevated()
    724         m += [None, MenuEntryDataCheckbox('Show debug info', setting=overviewSettings.show_debug_info)]
    725         return m
    726 
    727     def _GetDeletePresetsSubMenu(self):
    728         allPresets = self.presetSvc.GetAllPresets().copy()
    729         for name in self.presetSvc.GetDefaultOverviewPresetNames():
    730             if name in allPresets:
    731                 del allPresets[name]
    732 
    733         m = []
    734         for label in allPresets:
    735             m.append(MenuEntryData(label, lambda l = label: self.presetSvc.DeletePreset(l)))
    736 
    737         m.sort(key=lambda menuEntry: menuEntry.GetText())
    738         return m
    739 
    740     def Cleanup(self):
    741         pass
    742 
    743     @telemetry.ZONE_METHOD
    744     def UpdateAll(self, *args, **kwds):
    745         pass
    746 
    747     @telemetry.ZONE_METHOD
    748     def FullReload(self):
    749         self.StopOverviewUpdate()
    750         self.scroll.Clear()
    751         self._scrollNodesByItemID = {}
    752         self.FlagScrollEntriesAndBallparkDirty_InstantUpdate()
    753 
    754     def StopOverviewUpdate(self):
    755         if self.updateEntryOrderThread:
    756             self.updateEntryOrderThread.kill()
    757             self.updateEntryOrderThread = None
    758 
    759     def TriggerInstantUpdate(self, fromFunction = None):
    760         self.StopOverviewUpdate()
    761         if self.IsCollapsed() or self.IsMinimized():
    762             return
    763         self.UpdateEntryOrder()
    764 
    765     def FlagBallparkDirty(self, fromFunction = None):
    766         self._ballparkDirty = True
    767         if self.IsCollapsed() or self.IsMinimized():
    768             self.StopOverviewUpdate()
    769             return
    770         if not self.updateEntryOrderThread:
    771             self.updateEntryOrderThread = uthread.new(self.UpdateEntryOrder)
    772 
    773     def FlagScrollEntriesAndBallparkDirty_InstantUpdate(self, fromFunction = None):
    774         self._ballparkDirty = True
    775         self._scrollEntriesDirty = True
    776         self.TriggerInstantUpdate('FlagScrollEntriesDirtyDirty_InstantUpdate')
    777 
    778     def FlagScrollEntriesDirty_InstantUpdate(self, fromFunction = None):
    779         self._scrollEntriesDirty = True
    780         self.TriggerInstantUpdate('FlagScrollEntriesDirtyDirty_InstantUpdate')
    781 
    782     def FlagScrollEntriesDirty(self, fromFunction = None):
    783         self._scrollEntriesDirty = True
    784         if self.IsCollapsed() or self.IsMinimized():
    785             self.StopOverviewUpdate()
    786             return
    787         if not self.updateEntryOrderThread:
    788             self.updateEntryOrderThread = uthread.new(self.UpdateEntryOrder)
    789 
    790     @telemetry.ZONE_METHOD
    791     def _UpdateStaticDataForNodes(self, nodeList):
    792         structureProximityTrackerSvc = sm.GetService('structureProximityTracker')
    793         isDockingAccessUnknown = structureProximityTrackerSvc.HasDockingAccessChanged()
    794         emanationLockedGateID = GetLockedToGateID()
    795         usingLocalizationTooltips = localization.UseImportantTooltip()
    796         useSmallColorTags = self.presetSvc.GetSettingValueOrDefaultFromName(osConst.SETTING_NAME_SMALL_TAGS, False)
    797         useSmallText = self.presetSvc.GetSettingValueOrDefaultFromName(osConst.SETTING_NAME_SMALL_TEXT, False)
    798         columnWidths = self.sortHeaders.GetCurrentSizes()
    799         currentActive, _ = self.sortHeaders.GetCurrentActive()
    800         columnSettings, sortKeys = GetColumnSettingsAndSortKeys(currentActive, self.GetSelectedTabID())
    801         showIcon, sortIconIndex = columnSettings[COLUMN_ICON]
    802         showName, sortNameIndex = columnSettings[COLUMN_NAME]
    803         showDistance, sortDistanceIndex = columnSettings[COLUMN_DISTANCE]
    804         showSize, sortSizeIndex = columnSettings[COLUMN_SIZE]
    805         showAlliance, sortAllianceIndex = columnSettings[COLUMN_ALLIANCE]
    806         showType, sortTypeIndex = columnSettings[COLUMN_TYPE]
    807         showTag, sortTagIndex = columnSettings[COLUMN_TAG]
    808         showCorporation, sortCorporationIndex = columnSettings[COLUMN_CORPORATION]
    809         showFaction, sortFactionIndex = columnSettings[COLUMN_FACTION]
    810         showMilitia, sortMilitiaIndex = columnSettings[COLUMN_MILITIA]
    811         showVelocity, sortVelocityIndex = columnSettings[COLUMN_VELOCITY]
    812         showRadialVelocity, sortRadialVelocityIndex = columnSettings[COLUMN_RADIALVELOCITY]
    813         showAngularVelocity, sortAngularVelocityIndex = columnSettings[COLUMN_ANGULARVELOCITY]
    814         showTransversalVelocity, sortTransversalVelocityIndex = columnSettings[COLUMN_TRANSVERSALVELOCITY]
    815         defaultSortValue = [ 0 for _ in sortKeys ]
    816         inFleet = bool(session.fleetid)
    817         for node in nodeList:
    818             slimItem = node.slimItem()
    819             ball = node.ball()
    820             if not (ball and slimItem):
    821                 node.leavingOverview = True
    822                 if node.itemID in self._scrollNodesByItemID:
    823                     del self._scrollNodesByItemID[node.itemID]
    824                 continue
    825             self._scrollNodesByItemID[node.itemID] = node
    826             node.usingLocalizationTooltips = usingLocalizationTooltips
    827             node.useSmallText = useSmallText
    828             node.useSmallColorTags = useSmallColorTags
    829             node.decoClass.ENTRYHEIGHT = GetEntryHeight(self.compact)
    830             node.fontSize = GetEntryFontSize()
    831             node.columns = overviewColumns.GetColumns(self.GetSelectedTabID())
    832             node.columnWidths = columnWidths
    833             node.sortNameIndex = sortNameIndex
    834             node.sortDistanceIndex = sortDistanceIndex
    835             node.sortIconIndex = sortIconIndex
    836             node.sortTagIndex = sortTagIndex
    837             node.sortVelocityIndex = sortVelocityIndex
    838             node.sortRadialVelocityIndex = sortRadialVelocityIndex
    839             node.sortAngularVelocityIndex = sortAngularVelocityIndex
    840             node.sortTransversalVelocityIndex = sortTransversalVelocityIndex
    841             sortValue = defaultSortValue[:]
    842             node.sortValue = sortValue
    843             if node.display_NAME is None:
    844                 PrimeDisplayName(node)
    845             elif sortNameIndex is not None:
    846                 sortValue[sortNameIndex] = node.display_NAME.lower()
    847             if showType and slimItem.typeID:
    848                 if node.display_TYPE is None:
    849                     typeName = self.get_type_column_text(slimItem)
    850                     if usingLocalizationTooltips:
    851                         typeName, hint = PrepareLocalizationTooltip(typeName)
    852                         node.hint_TYPE = hint
    853                     node.display_TYPE = typeName
    854                 if sortTypeIndex is not None:
    855                     sortValue[sortTypeIndex] = node.display_TYPE.lower()
    856             if showSize:
    857                 size = ball.radius * 2
    858                 if node.display_SIZE is None:
    859                     node.display_SIZE = FmtDist(size)
    860                 if sortSizeIndex is not None:
    861                     sortValue[sortSizeIndex] = size
    862             if showTag:
    863                 if inFleet:
    864                     if node.display_TAG is None:
    865                         node.display_TAG = self.fleetSvc.GetTargetTag(node.itemID)
    866                 else:
    867                     node.display_TAG = ''
    868                 if sortTagIndex is not None:
    869                     tag = node.display_TAG
    870                     if tag is None:
    871                         sortValue[sortTagIndex] = 'zzz'
    872                     elif tag:
    873                         sortValue[sortTagIndex] = tag.lower()
    874                     else:
    875                         sortValue[sortTagIndex] = 0
    876             if showCorporation and slimItem.corpID:
    877                 node.display_CORPORATION = corpTag = GetCorpTickerName(slimItem)
    878                 if sortCorporationIndex is not None:
    879                     sortValue[sortCorporationIndex] = corpTag.lower()
    880             if showMilitia and slimItem.warFactionID:
    881                 militia = cfg.eveowners.Get(slimItem.warFactionID).name
    882                 if usingLocalizationTooltips:
    883                     militia, hint = PrepareLocalizationTooltip(militia)
    884                     node.hint_MILITIA = hint
    885                 node.display_MILITIA = militia
    886                 if sortMilitiaIndex is not None:
    887                     sortValue[sortMilitiaIndex] = militia.lower()
    888             if showAlliance and slimItem.allianceID:
    889                 node.display_ALLIANCE = alliance = GetAllianceTickerName(slimItem)
    890                 if sortAllianceIndex is not None:
    891                     sortValue[sortAllianceIndex] = alliance.lower()
    892             if showFaction:
    893                 if slimItem.ownerID and IsNPC(slimItem.ownerID) or slimItem.charID and IsNPC(slimItem.charID):
    894                     factionID = get_corporation_faction_id(slimItem.ownerID or slimItem.charID)
    895                     if factionID:
    896                         faction = cfg.eveowners.Get(factionID).name
    897                         if usingLocalizationTooltips:
    898                             faction, hint = PrepareLocalizationTooltip(faction)
    899                             node.hint_FACTION = hint
    900                         node.display_FACTION = faction
    901                         if sortFactionIndex is not None:
    902                             sortValue[sortFactionIndex] = faction.lower()
    903             if emanationLockedGateID and slimItem.groupID == const.groupStargate:
    904                 node.gateLockValue = GetGateLockValue(emanationLockedGateID, slimItem.itemID)
    905             if node.iconAndBackgroundFlags is None:
    906                 iconFlag, backgroundFlag = (0, 0)
    907                 if node.updateItem:
    908                     iconFlag, backgroundFlag = GetIconFlagAndBackgroundFlag(slimItem, self.stateSvc)
    909                 node.iconAndBackgroundFlags = (iconFlag, backgroundFlag)
    910             if slimItem.categoryID == const.categoryStructure and not IsFlexStructure(slimItem.typeID) and node.hasDockingRights in (DOCKING_ACCESS_UNKNOWN, None):
    911                 if isDockingAccessUnknown:
    912                     node.hasDockingRights = DOCKING_ACCESS_UNKNOWN
    913                 elif slimItem.state != STATE_UNANCHORED:
    914                     hasDockingRights = FindDockingRightsToDisplay(structureProximityTrackerSvc, slimItem)
    915                     node.hasDockingRights = hasDockingRights
    916             if sortIconIndex is not None:
    917                 iconFlag, backgroundFlag = node.iconAndBackgroundFlags
    918                 node.iconColor, colorSortValue = GetIconColor(slimItem, getSortValue=True)
    919                 if slimItem.categoryID == const.categoryStructure and colorSortValue:
    920                     iconFlag = colorSortValue
    921                 sortValue[sortIconIndex] = [iconFlag,
    922                  colorSortValue,
    923                  backgroundFlag,
    924                  slimItem.categoryID,
    925                  slimItem.groupID,
    926                  slimItem.typeID]
    927 
    928     def get_type_column_text(self, slim_item):
    929         if slim_item.groupID in (appConst.groupIce, appConst.groupHarvestableCloud, appConst.groupPhasedAsteroids):
    930             return evetypes.GetGroupNameByGroup(slim_item.groupID)
    931         if slim_item.categoryID == appConst.categoryAsteroid:
    932             return evetypes.GetCategoryNameByCategory(slim_item.categoryID)
    933         return evetypes.GetName(slim_item.typeID)
    934 
    935     @telemetry.ZONE_METHOD
    936     def _UpdateDynamicDataForNodes(self, nodeList, doYield = False):
    937         bp = self.michelle.GetBallpark(doWait=True)
    938         if not bp:
    939             self.FlagBallparkDirty('DoBallsAdded')
    940             return
    941         myBall = bp.GetBall(session.shipid)
    942         GetInvItem = bp.GetInvItem
    943         columns = overviewColumns.GetColumns(self.GetSelectedTabID())
    944         isDockingAccessUnknown = sm.GetService('structureProximityTracker').HasDockingAccessChanged()
    945         showDistance = COLUMN_DISTANCE in columns
    946         showIcon = COLUMN_ICON in columns
    947         homeStationID = None
    948         homeStationService = homestation.Service.instance()
    949         if homeStationService.is_home_station_data_loaded:
    950             homeStationID = homeStationService.get_home_station().id
    951         calculateAngularVelocity, calculateCombinedVelocity, calculateRadialNormal, calculateRadialVelocity, calculateTransveralVelocity, calculateVelocity, showVelocityCombined = GetColumnValuesToCalculate(columns)
    952         now = blue.os.GetSimTime()
    953         counter = 0
    954         for node in nodeList:
    955             ball = node.ball()
    956             slimItem = node.slimItem()
    957             if not slimItem:
    958                 slimItem = GetInvItem(node.itemID)
    959                 if slimItem:
    960                     node.slimItem = _weakref.ref(slimItem)
    961                     node.iconColor = None
    962                     PrimeDisplayName(node)
    963                     if node.panel:
    964                         if showIcon:
    965                             node.panel.UpdateIcon()
    966                             node.panel.UpdateIconColor()
    967                     UpdateIconAndBackgroundFlagsOnNode(node)
    968             if ball:
    969                 if showDistance:
    970                     ball.GetVectorAt(now)
    971                     node.rawDistance = rawDistance = max(ball.surfaceDist, 0)
    972                     if node.sortDistanceIndex is not None:
    973                         node.sortValue[node.sortDistanceIndex] = rawDistance
    974                 if showVelocityCombined and ball.isFree and myBall:
    975                     ball.GetVectorAt(now)
    976                     UpdateVelocityData(node, ball, myBall, calculateVelocity, calculateRadialVelocity, calculateCombinedVelocity, calculateRadialNormal, calculateTransveralVelocity, calculateAngularVelocity)
    977             self._UpdateStructureNode(node, slimItem, isDockingAccessUnknown)
    978             if homeStationID is not None:
    979                 node.isHomeStation = homeStationID == node.itemID
    980             if doYield:
    981                 counter += 1
    982                 if counter == 20:
    983                     blue.pyos.BeNice()
    984                     if self.destroyed:
    985                         self.StopOverviewUpdate()
    986                         return
    987                     counter = 0
    988 
    989     @telemetry.ZONE_METHOD
    990     def _UpdateStructureNode(self, node, slimItem, isDockingAccessUnknown):
    991         if not slimItem:
    992             return
    993         if slimItem.categoryID != const.categoryStructure:
    994             return
    995         itemID = slimItem.itemID
    996         if not sm.GetService('structureProximityTracker').IsStructureVisible(itemID):
    997             node.leavingOverview = True
    998             if itemID in self._scrollNodesByItemID:
    999                 del self._scrollNodesByItemID[itemID]
   1000         if IsFlexStructure(slimItem.typeID):
   1001             pass
   1002         elif isDockingAccessUnknown and node.hasDockingRights != DOCKING_ACCESS_UNKNOWN:
   1003             node.hasDockingRights = DOCKING_ACCESS_UNKNOWN
   1004             UpdateIconAndBackgroundFlagsOnNode(node)
   1005             if node.panel:
   1006                 node.panel.UpdateIcon()
   1007 
   1008     @telemetry.ZONE_METHOD
   1009     def CheckForNewEntriesAndRefreshScrollSetup(self):
   1010         ballpark = self.michelle.GetBallpark(doWait=True)
   1011         if ballpark is None:
   1012             return
   1013         if self.destroyed:
   1014             return
   1015         columns = overviewColumns.GetColumns(self.GetSelectedTabID())
   1016         self.sortHeaders.CreateColumns(columns, fixedColumns=FIXEDCOLUMNS)
   1017         newEntries = []
   1018         currentNotWanted = set()
   1019         with ScrollListLock:
   1020             if self._ballparkDirty:
   1021                 GetInvItem = ballpark.GetInvItem
   1022                 currentItemIDs = self._scrollNodesByItemID
   1023                 log.info('Overview - Checking ballpark for new entries')
   1024                 for itemID in ballpark.balls.keys():
   1025                     slimItem = GetInvItem(itemID)
   1026                     display = self.displayChecker.ShouldDisplayItem(slimItem)
   1027                     if not display:
   1028                         if itemID in currentItemIDs:
   1029                             currentNotWanted.add(itemID)
   1030                             if itemID in self._scrollNodesByItemID:
   1031                                 node = self._scrollNodesByItemID[itemID]
   1032                                 node.leavingOverview = True
   1033                                 del self._scrollNodesByItemID[itemID]
   1034                         continue
   1035                     if itemID not in currentItemIDs:
   1036                         newNode = self._ConstructScrollEntry(itemID, slimItem, ballpark)
   1037                         newEntries.append(newNode)
   1038 
   1039             nodeList = newEntries[:]
   1040             if self._scrollEntriesDirty:
   1041                 log.info('Overview - Update static data on current overview entries')
   1042                 nodeList.extend([ node for node in self.scroll.sr.nodes if node.itemID not in currentNotWanted ])
   1043             self._UpdateStaticDataForNodes(nodeList)
   1044             self.scroll.PurgeInvisibleEntries()
   1045             self.overviewSorted = False
   1046             if newEntries:
   1047                 self.scroll.ShowHint()
   1048                 self.scroll.AddNodes(-1, newEntries)
   1049         return newEntries
   1050 
   1051     def _ConstructScrollEntry(self, itemID, slimItem, ballpark):
   1052         updateItem = self.stateSvc.CheckIfUpdateItem(slimItem)
   1053         newNode = GetFromClass(OverviewScrollEntry, {'itemID': itemID,
   1054          'updateItem': updateItem})
   1055         ball = ballpark.GetBall(itemID)
   1056         newNode.ball = _weakref.ref(ball)
   1057         newNode.slimItem = _weakref.ref(slimItem)
   1058         if updateItem:
   1059             newNode.ewarGraphicIDs = self.GetEwarDataForNode(newNode)
   1060         newNode.ewarHints = self.ewarHintsByGraphicID
   1061         return newNode
   1062 
   1063     @telemetry.ZONE_METHOD
   1064     def UpdateEntryOrder(self, doYield = False):
   1065         if self.destroyed:
   1066             return
   1067         newEntries = False
   1068         if self._ballparkDirty or self._scrollEntriesDirty:
   1069             newEntries = self.CheckForNewEntriesAndRefreshScrollSetup()
   1070             if newEntries:
   1071                 doYield = False
   1072             self._ballparkDirty = False
   1073             self._scrollEntriesDirty = False
   1074         updateStartTime = blue.os.GetWallclockTimeNow()
   1075         try:
   1076             if not session.solarsystemid:
   1077                 self.StopOverviewUpdate()
   1078                 return
   1079             if self.IsCollapsed() or self.IsMinimized():
   1080                 self.StopOverviewUpdate()
   1081                 return
   1082             if self.destroyed:
   1083                 return
   1084             bp = self.michelle.GetBallpark(doWait=True)
   1085             if not bp:
   1086                 self.StopOverviewUpdate()
   1087                 return
   1088             columnWidths = self.sortHeaders.GetCurrentSizes()
   1089             broadcastsToTop = self.presetSvc.GetSettingValueBroadcastToTop()
   1090             fleetBroadcasts = self.fleetSvc.GetCurrentFleetBroadcasts()
   1091             mouseCoords = trinity.mainWindow.GetCursorPos()
   1092             if mouseCoords != self.prevMouseCoords:
   1093                 self.lastMovementTime = blue.os.GetWallclockTime()
   1094                 self.prevMouseCoords = mouseCoords
   1095             insider = IsUnder(uicore.uilib.mouseOver, self.scroll.GetContentContainer()) or uicore.uilib.mouseOver is self.scroll.GetContentContainer()
   1096             mouseMoving = blue.os.TimeDiffInMs(self.lastMovementTime, blue.os.GetWallclockTime()) > self.mouseMovementTimeout
   1097             mouseInsideApp = mouseCoords[0] > 0 and mouseCoords[0] < trinity.app.width and mouseCoords[1] > 0 and mouseCoords[1] < trinity.app.height
   1098             sortingFrozen = self.sortingFrozen = insider and mouseInsideApp and not mouseMoving or self._freezeOverview
   1099             if sortingFrozen:
   1100                 updateList = self.scroll.GetVisibleNodes()
   1101                 self.sortHeaders.SetSortIcon('res:/UI/Texture/classes/Overview/columnLock.png')
   1102             else:
   1103                 updateList = self.scroll.sr.nodes
   1104                 self.sortHeaders.SetSortIcon(None)
   1105             currentActive, currentDirection = self.sortHeaders.GetCurrentActive()
   1106 
   1107             def GetSortValue(_node):
   1108                 if broadcastsToTop:
   1109                     return GetSortValueWhenBroadcastGoToTop(_node, fleetBroadcasts, currentDirection)
   1110                 return _node.sortValue
   1111 
   1112             ballpark = self.michelle.GetBallpark(doWait=True)
   1113             if ballpark is None:
   1114                 return
   1115             self._UpdateDynamicDataForNodes(updateList, doYield=doYield)
   1116             counter = 0
   1117             nodesToRemove = []
   1118             for node in updateList:
   1119                 node.columnWidths = columnWidths
   1120                 if node.leavingOverview:
   1121                     if node.panel:
   1122                         node.panel.opacity = 0.25
   1123                         node.panel.state = uiconst.UI_DISABLED
   1124                     nodesToRemove.append(node)
   1125                     continue
   1126                 ball = node.ball()
   1127                 slimItem = node.slimItem()
   1128                 if not (slimItem and ball):
   1129                     node.leavingOverview = True
   1130                     if node.itemID in self._scrollNodesByItemID:
   1131                         del self._scrollNodesByItemID[node.itemID]
   1132                     if node.panel:
   1133                         node.panel.opacity = 0.25
   1134                         node.panel.state = uiconst.UI_DISABLED
   1135                     nodesToRemove.append(node)
   1136                     continue
   1137                 if doYield:
   1138                     counter += 1
   1139                     if counter == 20:
   1140                         blue.pyos.BeNice()
   1141                         if self.destroyed:
   1142                             self.StopOverviewUpdate()
   1143                             return
   1144                         counter = 0
   1145 
   1146             if doYield:
   1147                 blue.synchro.Yield()
   1148                 if self.destroyed:
   1149                     self.StopOverviewUpdate()
   1150                     return
   1151             if not sortingFrozen:
   1152                 if nodesToRemove:
   1153                     self.scroll.RemoveNodes(nodesToRemove)
   1154                 currentActive, currentDirection = self.sortHeaders.GetCurrentActive()
   1155                 with ScrollListLock:
   1156                     sortlist = sorted(self.scroll.sr.nodes, key=GetSortValue, reverse=not currentDirection)
   1157                     self.scroll.SetOrderedNodes(sortlist, loadNodes=False)
   1158                 self.overviewSorted = True
   1159             else:
   1160                 self.overviewSorted = False
   1161             self.LoadVisibleEntriesIfNeverLoaded()
   1162             self.CheckShowNoContentHint()
   1163         except Exception:
   1164             log.exception('Error updating inflight overview')
   1165             sys.exc_clear()
   1166 
   1167         if doYield:
   1168             diff = (gametime.GetWallclockTimeNow() - updateStartTime) / const.SEC
   1169             duration = max(overviewConst.ENTRY_ORDER_UPDATE_SLEEP - diff, overviewConst.ENTRY_ORDER_UPDATE_SLEEP_MIN)
   1170             uthread2.sleep(duration)
   1171         if not self.destroyed and (not self.updateEntryOrderThread or self.updateEntryOrderThread == stackless.getcurrent()):
   1172             self.updateEntryOrderThread = uthread.new(self.UpdateEntryOrder, doYield=True)
   1173 
   1174     def LoadVisibleEntriesIfNeverLoaded(self):
   1175         visibleNodes = self.scroll.GetVisibleNodes()
   1176         self._UpdateDynamicDataForNodes(visibleNodes)
   1177         for node in visibleNodes:
   1178             entry = node.panel
   1179             if entry and entry.display and not entry.hasLoaded:
   1180                 entry.Load(node)
   1181 
   1182     def LoadVisibleEntries(self):
   1183         visibleNodes = self.scroll.GetVisibleNodes()
   1184         self._UpdateDynamicDataForNodes(visibleNodes)
   1185         for node in visibleNodes:
   1186             entry = node.panel
   1187             if entry and entry.state != uiconst.UI_HIDDEN:
   1188                 entry.Load(node)
   1189 
   1190     def CheckShowNoContentHint(self):
   1191         if not self.scroll.sr.nodes:
   1192             self.scroll.ShowHint(localization.GetByLabel('UI/Common/NothingFound'))
   1193         else:
   1194             self.scroll.ShowHint()
   1195 
   1196     def OnFreezeOverviewWindows(self):
   1197         self._SetFreezeOverview(True)
   1198 
   1199     def OnUnfreezeOverviewWindows(self):
   1200         self._SetFreezeOverview(False)
   1201 
   1202     def _SetFreezeOverview(self, freeze = True):
   1203         triggerUpdate = False
   1204         if not freeze and freeze != self._freezeOverview:
   1205             triggerUpdate = True
   1206         self._freezeOverview = freeze
   1207         if triggerUpdate and getattr(self, 'overviewSorted', False) is False:
   1208             self.TriggerInstantUpdate('SetFreezeOverview')
   1209 
   1210     def UpdateForOneCharacter(self, charID, *args):
   1211         pass
   1212 
   1213     def OnExpanded(self, *args):
   1214         super(OverviewWindow, self).OnExpanded(*args)
   1215         self.TriggerInstantUpdate('OnExpanded')
   1216 
   1217     def OnCollapsed(self, *args):
   1218         super(OverviewWindow, self).OnCollapsed(*args)
   1219         self.StopOverviewUpdate()
   1220         self.cachedScrollPos = self.scroll.GetScrollProportion()
   1221 
   1222     def OnEndMaximize_(self, *args):
   1223         self.TriggerInstantUpdate('OnEndMaximize_')
   1224 
   1225     def OnEndMinimize_(self, *args):
   1226         self.StopOverviewUpdate()
   1227         self.cachedScrollPos = self.scroll.GetScrollProportion()
   1228 
   1229     def GetSelectedTabID(self):
   1230         if self.tabGroup:
   1231             return self.tabGroup.GetSelectedID()
   1232 
   1233     def OnSessionChanged(self, isRemote, session, change):
   1234         if self.destroyed:
   1235             return
   1236         if 'solarsystemid' in change:
   1237             self.scroll.Clear()
   1238             self._scrollNodesByItemID = {}
   1239         if 'shipid' in change:
   1240             self.FlushEwarStates()
   1241             self.FlagBallparkDirty('OnSessionChanged')
   1242 
   1243     def OnEnterSpace(self):
   1244         self.FlagBallparkDirty('OnEnterSpace')
   1245 
   1246     def OnOverviewOverrideChanged(self):
   1247         self.FlagBallparkDirty('OnOverviewOverrideChanged')
   1248 
   1249     def OnItemHighlighted(self, itemID, isActive):
   1250         node = self._scrollNodesByItemID.get(itemID, None)
   1251         if node is None:
   1252             return
   1253         if node.panel:
   1254             node.panel.UpdateTutorialHighlight(isActive)
   1255 
   1256     def OnStructuresVisibilityUpdated(self):
   1257         self.FlagBallparkDirty('OnStructuresVisibilityUpdated')
   1258 
   1259     def GetNextInRouteNodeAndDirection(self):
   1260         destinationPath = sm.GetService('starmap').GetDestinationPath()
   1261         if not destinationPath:
   1262             return (None, None)
   1263         nextInRouteItemID = destinationPath[0]
   1264         isSolarSystem = IsSolarSystem(nextInRouteItemID)
   1265         nextInRouteNode = None
   1266         for eachNode in self.scroll.GetNodes():
   1267             if eachNode.itemID == nextInRouteItemID:
   1268                 nextInRouteNode = eachNode
   1269                 break
   1270             if isSolarSystem and eachNode.slimItem:
   1271                 slimItem = eachNode.slimItem()
   1272                 if slimItem.groupID in (const.groupStargate, const.groupUpwellJumpGate):
   1273                     if slimItem.jumps and slimItem.jumps[0].locationID == nextInRouteItemID:
   1274                         nextInRouteNode = eachNode
   1275                         break
   1276 
   1277         if not nextInRouteNode:
   1278             return (None, None)
   1279         if nextInRouteNode.panel and nextInRouteNode.panel.IsVisible():
   1280             return (nextInRouteNode.panel, None)
   1281         myNodeIdx = nextInRouteNode.idx
   1282         visibleNodes = self.scroll.GetVisibleNodes()
   1283         if visibleNodes:
   1284             firstNode = visibleNodes[0]
   1285             lastNode = visibleNodes[-1]
   1286             if firstNode.idx > myNodeIdx:
   1287                 pointToInstead = self.sortHeaders or firstNode.panel
   1288                 return (pointToInstead, 'up')
   1289             if lastNode.idx < myNodeIdx:
   1290                 pointToInstead = lastNode.panel
   1291                 return (pointToInstead, 'down')
   1292         return (None, None)
   1293 
   1294     def OnEmanationLockUpdated(self, lockDetails):
   1295         if not lockDetails or lockDetails.solar_system_id != session.solarsystemid2:
   1296             return
   1297         self._UpdateEmanationLock()
   1298 
   1299     def OnEmanationGateLockRemoved(self, *args):
   1300         self._UpdateEmanationLock()
   1301 
   1302     def _UpdateEmanationLock(self):
   1303         systemInfo = cfg.mapSolarSystemContentCache.get(session.solarsystemid2, None)
   1304         if not systemInfo:
   1305             return
   1306         for gateID in systemInfo.stargates.iterkeys():
   1307             node = self._scrollNodesByItemID.get(gateID, None)
   1308             if not node:
   1309                 continue
   1310             slimItem = node.slimItem()
   1311             node.gateLockValue = GetGateLockValueForSlimItem(slimItem)
   1312             if node.panel:
   1313                 UpdateIconAndBackgroundFlagsOnNode(node)
   1314 
   1315 
   1316 class BracketButtonIcon(ButtonIcon):
   1317     expandOnLeft = True
   1318 
   1319     def GetMenu(self, *args):
   1320         return GetBracketVisibilitySubMenu()