mining_scan_results_window.py (15438B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\inflight\mining_scan_results_window.py 2 import sys 3 import appConst 4 import collections 5 import eveicon 6 import evelink.client 7 import evetypes 8 import localization 9 import mining 10 import utillib 11 from carbon.common.script.sys.serviceManager import ServiceManager 12 from carbon.common.script.util.format import FmtAmt, FmtDist 13 from carbonui import uiconst, Align, PickState 14 from carbonui.button.group import ButtonGroup 15 from carbonui.control.button import Button 16 from carbonui.control.contextMenu.menuData import MenuData 17 from carbonui.control.window import Window 18 from carbonui.loggers.buttonLogger import log_button_clicked 19 from carbonui.primitives.containerAutoSize import ContainerAutoSize 20 from carbonui.primitives.warningContainer import WarningContainer 21 from carbonui.uicore import uicore 22 from carbonui.window.control.action import WindowMenuAction 23 from carbonui.window.segment.underlay import WindowSegmentUnderlay 24 from clientsettings.setting import CharSettingEnum 25 from dogma import units 26 from eve.client.script.parklife import states as state, states 27 from eve.client.script.parklife.state import GetStateSvc 28 from eve.client.script.remote.michelle import GetMichelle 29 from eve.client.script.ui import eveColor 30 from eve.client.script.ui.control import eveScroll 31 from eve.client.script.ui.control.entries.generic import Generic 32 from eve.client.script.ui.control.entries.util import GetFromClass 33 from eve.client.script.ui.control.listgroup import ListGroup 34 from eve.client.script.ui.inflight.bracketsAndTargets.targetIconCont import TargetIconCont 35 from eve.common.script.util.eveFormat import FmtISKAndRound 36 from eveservices.menu import GetMenuService 37 from inventorycommon import typeHelpers 38 from localization import GetByLabel 39 from mining import MiningScan 40 from mining.client import mining_signals, mining_util 41 from mining.client.asteroid_scanning_service import AsteroidScanningService 42 from mining.client.mining_overlay_controller import MiningOverlayController 43 SCROLL_HEADERS_TIER_2 = (GetByLabel('UI/Common/Ore'), 44 GetByLabel('UI/Common/Quantity'), 45 GetByLabel('UI/Common/Volume'), 46 GetByLabel('UI/Common/Value'), 47 GetByLabel('UI/Common/Distance')) 48 49 class SortGroupsBy(): 50 ALPHABETICALLY = 1 51 VALUE = 2 52 TYPE = 3 53 54 55 sort_groups_by_setting = CharSettingEnum('miningScanSortGroupsBy', default_value=SortGroupsBy.ALPHABETICALLY) 56 57 class MiningScanResultsWindow(Window): 58 default_windowID = 'MiningScanResultsWindow2' 59 default_isLightBackground = True 60 default_scope = uiconst.SCOPE_INFLIGHT 61 default_iconNum = eveicon.mining_overlay_toggle_default 62 default_caption = GetByLabel('UI/Inflight/Scanner/MiningScanResults') 63 default_width = 400 64 default_height = 400 65 analyticID = default_windowID 66 67 def __init__(self, *args, **kwargs): 68 super(MiningScanResultsWindow, self).__init__(*args, **kwargs) 69 self.construct_layout() 70 ServiceManager.Instance().RegisterForNotifyEvent(self, 'OnStateChange') 71 ServiceManager.Instance().RegisterForNotifyEvent(self, 'OnSessionChanged') 72 self.populate_scroll() 73 self.connect_signals() 74 75 def construct_layout(self): 76 bottom_cont = ContainerAutoSize(parent=self.content, align=Align.TOBOTTOM, padTop=4) 77 btn_group = ButtonGroup(parent=bottom_cont, align=Align.TOBOTTOM, padTop=4) 78 pad_left, _, pad_right, pad_bottom = self.content_padding 79 WindowSegmentUnderlay(bgParent=bottom_cont, padding=(-pad_left, 80 0, 81 -pad_right, 82 -pad_bottom)) 83 tier = mining_util.get_current_ship_mining_scanner_upgrade_tier() 84 if not tier or tier < MiningScan.TIER_2: 85 WarningContainer(parent=bottom_cont, align=Align.TOBOTTOM, pickState=PickState.ON, textAlignment=Align.TOTOP, text=GetByLabel('UI/Mining/MiningScannerUpgradeRequired', module_name=evelink.type_link(appConst.typeSurveyScannerI, evetypes.GetName(appConst.typeSurveyScannerI)))) 86 self.scan_button = Button(parent=btn_group, label=GetByLabel('UI/Inflight/Scanner/Scan'), func=self.on_scan_button, analyticID='MiningScanButton') 87 self.scroll = eveScroll.Scroll(parent=self.content) 88 self.scroll.OnChar = lambda *_args: None 89 self.scroll.sr.id = 'mining_scan_scroll' 90 91 def on_scan_button(self, *args): 92 MiningOverlayController.get_instance().perform_scan() 93 log_button_clicked(self.scan_button) 94 95 @classmethod 96 def Open(cls, *args, **kwds): 97 if not mining_util.has_current_ship_integrated_mining_scanner(): 98 return 99 return super(MiningScanResultsWindow, cls).Open(*args, **kwds) 100 101 def Close(self, setClosed = False, *args, **kwds): 102 super(MiningScanResultsWindow, self).Close(setClosed, *args, **kwds) 103 self.disconnect_signals() 104 105 def GetCustomHeaderButtons(self): 106 return [WindowMenuAction(window=self, icon=eveicon.bars_sort_ascending, label=GetByLabel('UI/Mining/SortGroupsBy'), callback=self.on_filter_button)] 107 108 def on_filter_button(self, *args): 109 m = MenuData() 110 m.AddLabel(GetByLabel('UI/Mining/SortGroupsBy')) 111 m.AddRadioButton(GetByLabel('UI/Mining/SortGroupsByAlphabetically'), SortGroupsBy.ALPHABETICALLY, sort_groups_by_setting) 112 m.AddRadioButton(GetByLabel('UI/Mining/SortGroupsByValue'), SortGroupsBy.VALUE, sort_groups_by_setting) 113 m.AddRadioButton(GetByLabel('UI/Mining/SortGroupsByType'), SortGroupsBy.TYPE, sort_groups_by_setting) 114 return m 115 116 def disconnect_signals(self): 117 mining_signals.on_mining_scan_completed.disconnect(self.on_mining_scan_completed) 118 mining_signals.on_mining_scan_effect_started.disconnect(self.on_mining_scan_effect_started) 119 mining_signals.on_mining_scan_effect_completed.disconnect(self.on_mining_scan_effect_completed) 120 sort_groups_by_setting.on_change.disconnect(self.on_sort_groups_by_setting) 121 122 def connect_signals(self): 123 mining_signals.on_mining_scan_completed.connect(self.on_mining_scan_completed) 124 mining_signals.on_mining_scan_effect_started.connect(self.on_mining_scan_effect_started) 125 mining_signals.on_mining_scan_effect_completed.connect(self.on_mining_scan_effect_completed) 126 sort_groups_by_setting.on_change.connect(self.on_sort_groups_by_setting) 127 128 def on_sort_groups_by_setting(self, *args): 129 self.populate_scroll() 130 131 def on_mining_scan_effect_started(self): 132 self.scan_button.busy = True 133 134 def on_mining_scan_effect_completed(self): 135 self.scan_button.busy = False 136 137 def on_mining_scan_completed(self): 138 self.populate_scroll() 139 140 def populate_scroll(self): 141 asteroid_ids_and_quantities_by_type_id = self.get_asteroid_ids_and_quantities_by_type_ids() 142 scan_tier = AsteroidScanningService.get_instance().get_current_scan_type() 143 if scan_tier == MiningScan.TIER_2: 144 scroll_list = self.get_tier_2_scan_scroll_list(asteroid_ids_and_quantities_by_type_id) 145 else: 146 scroll_list = self.get_tier_1_scan_entries(asteroid_ids_and_quantities_by_type_id) 147 scroll_list = self.get_groups_sorted(scroll_list) 148 self.scroll.Load(contentList=scroll_list, headers=SCROLL_HEADERS_TIER_2, noContentHint=GetByLabel('UI/Mining/NoScanResultsFound')) 149 150 def get_groups_sorted(self, scroll_list): 151 if sort_groups_by_setting.get() == SortGroupsBy.ALPHABETICALLY: 152 scroll_list = localization.util.Sort(scroll_list, key=lambda x: x.label) 153 elif sort_groups_by_setting.get() == SortGroupsBy.VALUE: 154 scroll_list = sorted(scroll_list, key=lambda x: mining_util.get_volume_est_price(x['typeID']), reverse=True) 155 elif sort_groups_by_setting.get() == SortGroupsBy.TYPE: 156 scroll_list = sorted(scroll_list, key=lambda x: (evetypes.GetGroupID(x['typeID']), x['typeID'])) 157 return scroll_list 158 159 def get_tier_1_scan_entries(self, asteroid_ids_and_quantities_by_type_id): 160 scroll_list = [] 161 for type_id, asteroid_ids_and_quantities in asteroid_ids_and_quantities_by_type_id.items(): 162 scroll_list.append(GetFromClass(ListGroup, {'GetSubContent': self.get_entries_tier_1, 163 'label': evetypes.GetName(type_id), 164 'id': ('TypeSel', type_id), 165 'groupItems': asteroid_ids_and_quantities, 166 'typeID': type_id, 167 'showlen': 1, 168 'sublevel': 0, 169 'state': 'locked', 170 'currentTargetIDs': sm.GetService('target').GetTargets().keys(), 171 'iconID': sm.GetService('bracket').GetBracketIcon(type_id), 172 'posttext': self.get_list_group_post_text(type_id)})) 173 174 return scroll_list 175 176 def get_tier_2_scan_scroll_list(self, asteroid_ids_and_quantities_by_type_id): 177 scroll_list = [] 178 for type_id, asteroid_ids_and_quantities in asteroid_ids_and_quantities_by_type_id.items(): 179 scroll_list.append(GetFromClass(ListGroup, {'GetSubContent': self.get_entries_tier_2, 180 'label': evetypes.GetName(type_id), 181 'id': ('TypeSel', type_id), 182 'groupItems': asteroid_ids_and_quantities, 183 'typeID': type_id, 184 'showlen': 1, 185 'sublevel': 0, 186 'state': 'locked', 187 'currentTargetIDs': sm.GetService('target').GetTargets().keys(), 188 'iconID': sm.GetService('bracket').GetBracketIcon(type_id), 189 'posttext': self.get_list_group_post_text(type_id)})) 190 191 return scroll_list 192 193 def get_list_group_post_text(self, type_id): 194 volume_price = mining_util.get_volume_est_price(type_id) 195 if not volume_price: 196 return '' 197 est_price = GetByLabel('UI/Shared/EstimatedPricePerSquareMeter', iskString=FmtISKAndRound(volume_price, False)) 198 return u"<color='{}'>{}</color>".format(eveColor.LED_GREY_HEX, est_price) 199 200 def get_asteroid_ids_and_quantities_by_type_ids(self): 201 quantity_by_asteroid_id = AsteroidScanningService.get_instance().get_quantities_by_asteroid_id() 202 asteroid_ids_and_quantities_by_type_id = collections.defaultdict(list) 203 ballpark = GetMichelle().GetBallpark() 204 for asteroid_id, quantity in quantity_by_asteroid_id.items(): 205 ball = ballpark.GetBall(asteroid_id) 206 type_id = mining.get_harvestable_type(ball.typeID) 207 asteroid_ids_and_quantities_by_type_id[type_id].append((asteroid_id, quantity)) 208 209 return asteroid_ids_and_quantities_by_type_id 210 211 def get_entries_tier_1(self, node, *args): 212 scroll_list = [] 213 bp = GetMichelle().GetBallpark() 214 for asteroid_id, _ in node.groupItems: 215 try: 216 dist = bp.GetBall(asteroid_id).surfaceDist 217 except AttributeError: 218 dist = 0 219 import traceback 220 traceback.print_exc() 221 sys.exc_clear() 222 223 data = utillib.KeyVal() 224 evetypes.GetVolume(node.typeID) 225 rightLabelParts = ['-', 226 '-', 227 '-', 228 FmtDist(dist)] 229 data.label = '%s<t><right>%s' % (evetypes.GetName(node.typeID), '<t><right>'.join(rightLabelParts)) 230 data.itemID = asteroid_id 231 data.typeID = node.typeID 232 data.showinfo = 0 233 data.isTarget = asteroid_id in node.currentTargetIDs 234 data.sublevel = 1 235 data.Set('sort_' + GetByLabel('UI/Common/Distance'), dist) 236 entry = GetFromClass(MiningScanEntry, data) 237 scroll_list.append(entry) 238 239 return scroll_list 240 241 def get_entries_tier_2(self, node, *args): 242 scroll_list = [] 243 bp = sm.GetService('michelle').GetBallpark() 244 for asteroid_id, qty in node.groupItems: 245 try: 246 dist = bp.GetBall(asteroid_id).surfaceDist 247 except AttributeError: 248 dist = 0 249 import traceback 250 traceback.print_exc() 251 sys.exc_clear() 252 253 data = utillib.KeyVal() 254 evetypes.GetVolume(node.typeID) 255 volume = evetypes.GetVolume(node.typeID) * qty 256 value = typeHelpers.GetAveragePrice(node.typeID) 257 if value: 258 value = FmtISKAndRound(value * qty) 259 else: 260 value = '-' 261 volumeText = GetByLabel('UI/InfoWindow/ValueAndUnit', value=FmtAmt(volume, showFraction=0), unit=units.get_display_name(appConst.unitVolume)) 262 rightLabelParts = [FmtAmt(qty), 263 volumeText, 264 value, 265 FmtDist(dist)] 266 data.label = '%s<t><right>%s' % (evetypes.GetName(node.typeID), '<t><right>'.join(rightLabelParts)) 267 data.itemID = asteroid_id 268 data.typeID = node.typeID 269 data.showinfo = 0 270 data.isTarget = asteroid_id in node.currentTargetIDs 271 data.sublevel = 1 272 data.Set('sort_' + GetByLabel('UI/Common/Quantity'), qty) 273 data.Set('sort_' + GetByLabel('UI/Common/Volume'), volume) 274 data.Set('sort_' + GetByLabel('UI/Common/Value'), value) 275 data.Set('sort_' + GetByLabel('UI/Common/Distance'), dist) 276 entry = GetFromClass(MiningScanEntry, data) 277 scroll_list.append(entry) 278 279 return scroll_list 280 281 def OnStateChange(self, itemID, flag, newState, *args): 282 if flag not in (state.targeting, state.targeted): 283 return 284 for node in self.scroll.GetNodes(): 285 if node.itemID == itemID and node.panel is not None: 286 node.panel.OnChangingState(flag, newState) 287 return 288 289 def OnSessionChanged(self, isremote, session, change): 290 if 'shipid' in change: 291 self.Close() 292 293 294 class MiningScanEntry(Generic): 295 296 def ApplyAttributes(self, attributes): 297 super(MiningScanEntry, self).ApplyAttributes(attributes) 298 self.targetIconCont = TargetIconCont(parent=self, pos=(2, 0, 16, 16), iconSize=16, align=uiconst.CENTERLEFT) 299 300 def Load(self, node): 301 sublevelCorrection = self.sr.node.scroll.sr.get('sublevelCorrection', 0) if self.sr.node.scroll else 0 302 node.sublevel += sublevelCorrection 303 Generic.Load(self, node) 304 targeting, targeted = sm.GetService('stateSvc').GetStates(node.itemID, [state.targeting, state.targeted]) 305 if targeting: 306 self.targetIconCont.StartTargeting() 307 elif targeted: 308 self.targetIconCont.SetTargetedIcon() 309 310 def OnChangingState(self, flag, newState): 311 if flag == state.targeting: 312 self.targetIconCont.HandleTargetingChanges(newState) 313 elif flag == state.targeted: 314 self.targetIconCont.HandleTargetedChanges(newState) 315 316 def OnClick(self, *args): 317 super(MiningScanEntry, self).OnClick(*args) 318 uicore.cmd.ExecuteCombatCommand(self.sr.node.itemID, uiconst.UI_CLICK) 319 GetStateSvc().SetState(self.sr.node.itemID, states.selected, True) 320 321 def OnMouseEnter(self, *args): 322 super(MiningScanEntry, self).OnMouseEnter(*args) 323 GetStateSvc().SetState(self.sr.node.itemID, states.mouseOver, True) 324 325 def OnMouseExit(self, *args): 326 super(MiningScanEntry, self).OnMouseExit(*args) 327 GetStateSvc().SetState(self.sr.node.itemID, states.mouseOver, False) 328 329 def GetMenu(self, *args): 330 return GetMenuService().CelestialMenu(self.sr.node.itemID)