guests.py (15361B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\shared\dockedUI\guests.py 2 import eveicon 3 import logging 4 import blue 5 import localization 6 import signals 7 import uthread2 8 import utillib 9 from bannedwords.client import bannedwords 10 from carbon.common.script.sys.serviceManager import ServiceManager 11 from carbonui import Density, uiconst 12 from carbonui.control.basicDynamicScroll import BasicDynamicScroll 13 from carbonui.control.contextMenu.menuData import MenuData 14 from carbonui.primitives.container import Container 15 from carbonui.primitives.containerAutoSize import ContainerAutoSize 16 from clientsettings.setting import UserSettingBool 17 from carbonui.util.various_unsorted import SortListOfTuples 18 from eve.client.script.ui.control.entries.util import GetFromClass 19 from carbonui.button.menu import MenuButtonIcon 20 from eve.client.script.ui.quickFilter import QuickFilterEdit 21 from eve.client.script.ui.shared.userentry import User, UserSimple 22 from eve.common.script.sys import idCheckers 23 logger = logging.getLogger(__name__) 24 guest_list_compact_mode_setting = UserSettingBool('guestCondensedUserList', False) 25 26 class LoadState(object): 27 UNLOADED = 1 28 LOADING = 2 29 UNLOAD_PENDING = 3 30 LOADED = 4 31 ERROR = 5 32 33 34 class GuestsPanelController(object): 35 __notifyevents__ = ('OnCharNoLongerInStation', 'OnCharNowInStation', 'OnCharacterEnteredStructure', 'OnCharacterLeftStructure', 'OnSessionChanged') 36 _guest_list = None 37 _location_id = None 38 39 def __init__(self): 40 self._load_state = LoadState.UNLOADED 41 self._guests_pending_add = {} 42 self._guests_pending_remove = set() 43 self.on_guest_list_changed = signals.Signal('{}.on_guest_list_changed'.format(self.__class__.__name__)) 44 self.on_loading_changed = signals.Signal('{}.on_loading_changed'.format(self.__class__.__name__)) 45 self.on_guest_count_changed = signals.Signal('{}.on_guest_count_changed'.format(self.__class__.__name__)) 46 47 @property 48 def guest_list(self): 49 if self._load_state == LoadState.LOADED: 50 return self._guest_list 51 52 @property 53 def guest_count(self): 54 if self._load_state == LoadState.LOADED: 55 return len(self._guest_list.guests) 56 else: 57 return 0 58 59 @property 60 def loading(self): 61 return self._load_state in {LoadState.LOADING, LoadState.UNLOAD_PENDING} 62 63 def load(self): 64 if self._load_state == LoadState.UNLOADED: 65 ServiceManager.Instance().RegisterNotify(self) 66 self._load_guest_list(location_id=self._get_location_id_from_session(session)) 67 68 def unload(self): 69 if self._load_state == LoadState.LOADING: 70 self._set_load_state(LoadState.UNLOAD_PENDING) 71 else: 72 ServiceManager.Instance().UnregisterNotify(self) 73 self._guests_pending_add.clear() 74 self._guests_pending_remove.clear() 75 self._guest_list = None 76 self._set_load_state(LoadState.UNLOADED) 77 self.on_guest_list_changed(self) 78 self.on_guest_count_changed(self) 79 80 def _set_load_state(self, value): 81 if self._load_state != value: 82 was_loading = self.loading 83 self._load_state = value 84 if self.loading != was_loading: 85 self.on_loading_changed(self) 86 87 @staticmethod 88 def _get_station_guests(): 89 guests_raw = ServiceManager.Instance().GetService('station').GetGuests() 90 return [ Guest(character_id, corporation_id, alliance_id, war_faction_id) for character_id, (corporation_id, alliance_id, war_faction_id) in guests_raw.items() ] 91 92 @staticmethod 93 def _get_structure_guests(): 94 guests_raw = ServiceManager.Instance().GetService('structureGuests').GetGuests() 95 return [ Guest(character_id, corporation_id, alliance_id, war_faction_id) for character_id, (corporation_id, alliance_id, war_faction_id) in guests_raw.items() ] 96 97 def _load_guest_list(self, location_id): 98 if self._location_id == location_id: 99 return 100 self._location_id = location_id 101 self._guests_pending_add.clear() 102 self._guests_pending_remove.clear() 103 self._set_load_state(LoadState.LOADING) 104 try: 105 guest_list = None 106 if idCheckers.IsStation(location_id): 107 guest_list = GuestList(location_id=session.stationid, guests=self._get_station_guests()) 108 elif location_id is not None: 109 guest_list = GuestList(location_id=session.structureid, guests=self._get_structure_guests()) 110 if self._location_id != location_id: 111 return 112 if self._load_state == LoadState.UNLOAD_PENDING: 113 self.unload() 114 return 115 if guest_list is not None: 116 for guest in self._guests_pending_add.values(): 117 guest_list.add(guest) 118 119 for character_id in self._guests_pending_remove: 120 guest_list.discard(character_id) 121 122 self._guests_pending_add.clear() 123 self._guests_pending_remove.clear() 124 except Exception: 125 logger.exception('Unhandled exception while attempting to load a guest list') 126 if self._location_id == location_id: 127 self._set_load_state(LoadState.ERROR) 128 self.unload() 129 else: 130 self._guest_list = guest_list 131 if guest_list is not None: 132 self._set_load_state(LoadState.LOADED) 133 else: 134 self._set_load_state(LoadState.UNLOADED) 135 self.on_guest_list_changed(self) 136 self.on_guest_count_changed(self) 137 138 @staticmethod 139 def _get_location_id_from_session(session): 140 if session.stationid: 141 return session.stationid 142 elif session.structureid: 143 return session.structureid 144 else: 145 return None 146 147 def _add_guest(self, guest): 148 if self.loading: 149 self._guests_pending_remove.discard(guest.character_id) 150 self._guests_pending_add[guest.character_id] = guest 151 elif self._load_state == LoadState.LOADED: 152 self._guest_list.add(guest) 153 self.on_guest_count_changed(self) 154 155 def _remove_guest(self, character_id): 156 if self.loading: 157 if character_id in self._guests_pending_add: 158 self._guests_pending_add.pop(character_id) 159 self._guests_pending_remove.add(character_id) 160 elif self._load_state == LoadState.LOADED: 161 self._guest_list.discard(character_id) 162 self.on_guest_count_changed(self) 163 164 def OnCharNowInStation(self, rec): 165 character_id, corporation_id, alliance_id, war_faction_id = rec 166 self._add_guest(Guest(character_id, corporation_id, alliance_id, war_faction_id)) 167 168 def OnCharNoLongerInStation(self, rec): 169 self._remove_guest(character_id=rec[0]) 170 171 def OnCharacterEnteredStructure(self, characterID, corporationID, allianceID, factionID): 172 self._add_guest(Guest(character_id=characterID, corporation_id=corporationID, alliance_id=allianceID, war_faction_id=factionID)) 173 174 def OnCharacterLeftStructure(self, characterID): 175 self._remove_guest(character_id=characterID) 176 177 def OnSessionChanged(self, isRemote, session, change): 178 if any((name in change for name in ('stationid', 'structureid'))): 179 uthread2.start_tasklet(self._load_guest_list, location_id=self._get_location_id_from_session(session)) 180 181 182 class GuestsPanel(Container): 183 _loaded = False 184 _quick_filter = None 185 _scroll = None 186 _scroll_update_id = 0 187 188 def __init__(self, controller = None, **kwargs): 189 self._controller = controller or GuestsPanelController() 190 super(GuestsPanel, self).__init__(**kwargs) 191 192 def LoadPanel(self): 193 if not self._loaded: 194 self._loaded = True 195 self._layout() 196 self._subscribe() 197 uthread2.start_tasklet(self._load) 198 199 def _load(self): 200 self._controller.load() 201 self._update_scroll_content() 202 203 def _layout(self): 204 top_cont = ContainerAutoSize(parent=self, align=uiconst.TOTOP, alignMode=uiconst.TOPLEFT) 205 self._quick_filter = QuickFilterEdit(name='quickFilterEdit', parent=top_cont, align=uiconst.TOPLEFT, isCharacterField=True, density=Density.COMPACT) 206 self._quick_filter.ReloadFunction = self._update_scroll_filter 207 self._scroll = BasicDynamicScroll(parent=self, padTop=8) 208 self._update_scroll_loading() 209 MenuButtonIcon(parent=top_cont, align=uiconst.CENTERRIGHT, get_menu_func=get_guest_list_settings, texturePath=eveicon.people, hint=localization.GetByLabel('UI/Chat/UserlistSettingButtonHint'), width=16, height=16, iconSize=16) 210 211 def _update_scroll_filter(self): 212 filter_query = self._quick_filter.GetValue() 213 if len(filter_query): 214 bannedwords.check_search_words_allowed(filter_query) 215 self._update_scroll_content() 216 217 def _update_scroll_loading(self): 218 if self._scroll: 219 if self._controller.loading: 220 self._scroll.ShowLoading() 221 222 def _update_scroll_content(self): 223 if not self._scroll or self._scroll.destroyed: 224 return 225 scroll_update_id = self._allocate_scroll_update_id() 226 guest_list = self._controller.guest_list 227 if guest_list is None: 228 self._location_id = None 229 self._scroll.Clear() 230 self._scroll.HideLoading() 231 else: 232 self._location_id = guest_list.location_id 233 guest_list.on_guest_added.connect(self._on_guest_list_guest_added) 234 guest_list.on_guest_removed.connect(self._on_guest_list_guest_removed) 235 guests = self._get_filtered_guests(guest_list.guests) 236 entries = self._get_guest_entries(guests) 237 if self._scroll_update_id == scroll_update_id: 238 if self._scroll.destroyed: 239 return 240 self._scroll.Clear() 241 self._scroll.AddNodes(0, entries) 242 self._scroll.HideLoading() 243 244 def _allocate_scroll_update_id(self): 245 self._scroll_update_id += 1 246 return self._scroll_update_id 247 248 def _on_guest_list_guest_added(self, guest): 249 self._add_guest_to_scroll(guest) 250 251 def _on_guest_list_guest_removed(self, guest): 252 self._remove_guest_from_scroll(guest) 253 254 def _add_guest_to_scroll(self, guest): 255 if not self._scroll or self._scroll.destroyed: 256 return 257 nodes = self._scroll.GetNodes() 258 index = bisect_left(nodes, guest.name.lower(), key=lambda node: node.info.name.lower()) 259 entry = self._get_guest_entry(guest) 260 self._scroll.AddNodes(index, [entry]) 261 262 def _remove_guest_from_scroll(self, guest): 263 if self._scroll and not self._scroll.destroyed: 264 for node in self._scroll.GetNodes(): 265 if node.charID == guest.character_id: 266 self._scroll.RemoveNodes([node]) 267 268 def _get_filtered_guests(self, guests): 269 cfg.eveowners.Prime([ guest.character_id for guest in guests ]) 270 filter_query = self._quick_filter.GetValue() 271 if len(filter_query): 272 filtered_guests = [] 273 for guest in guests: 274 if self._quick_filter.QuickFilter(rec=utillib.KeyVal(name=guest.name.lower())): 275 filtered_guests.append(guest) 276 blue.pyos.BeNice() 277 278 return filtered_guests 279 else: 280 return guests 281 282 def _get_guest_entries(self, guests): 283 owner_ids = [ guest.character_id for guest in guests ] 284 cfg.eveowners.Prime(owner_ids) 285 entries = [] 286 for guest in guests: 287 entries.append((guest.name.lower(), self._get_guest_entry(guest))) 288 blue.pyos.BeNice() 289 290 return SortListOfTuples(entries) 291 292 @staticmethod 293 def _get_guest_entry(guest): 294 return GetFromClass(entryType=get_guest_entry_class(), data={'charID': guest.character_id, 295 'info': cfg.eveowners.Get(guest.character_id), 296 'label': guest.name, 297 'corpID': guest.corporation_id, 298 'allianceID': guest.alliance_id, 299 'warFactionID': guest.war_faction_id}) 300 301 def _subscribe(self): 302 guest_list_compact_mode_setting.on_change.connect(self._on_guest_list_compact_mode_changed) 303 self._controller.on_guest_list_changed.connect(self._on_guest_list_changed) 304 self._controller.on_loading_changed.connect(self._on_loading_changed) 305 306 def _on_loading_changed(self, controller): 307 self._update_scroll_loading() 308 309 def _on_guest_list_changed(self, controller): 310 uthread2.start_tasklet(self._update_scroll_content) 311 312 def _on_guest_list_compact_mode_changed(self, value): 313 uthread2.start_tasklet(self._update_scroll_content) 314 315 316 class GuestList(object): 317 318 def __init__(self, location_id, guests = None): 319 self._location_id = location_id 320 self._guests = {} 321 self.on_guest_added = signals.Signal('{}.on_guest_added'.format(self.__class__.__name__)) 322 self.on_guest_removed = signals.Signal('{}.on_guest_removed'.format(self.__class__.__name__)) 323 if guests: 324 for guest in guests: 325 self._guests[guest.character_id] = guest 326 327 @property 328 def guests(self): 329 return self._guests.values() 330 331 @property 332 def guests_by_character_id(self): 333 return self._guests.copy() 334 335 @property 336 def location_id(self): 337 return self._location_id 338 339 def add(self, guest): 340 self._guests[guest.character_id] = guest 341 self.on_guest_added(guest) 342 343 def remove(self, character_id): 344 guest = self._guests.pop(character_id) 345 self.on_guest_removed(guest) 346 return guest 347 348 def discard(self, character_id): 349 if character_id in self._guests: 350 return self.remove(character_id) 351 352 353 class Guest(object): 354 355 def __init__(self, character_id, corporation_id, alliance_id, war_faction_id): 356 self.character_id = character_id 357 self.corporation_id = corporation_id 358 self.alliance_id = alliance_id 359 self.war_faction_id = war_faction_id 360 361 @property 362 def name(self): 363 return cfg.eveowners.Get(self.character_id).name 364 365 @property 366 def owner_info(self): 367 return cfg.eveowners.Get(self.character_id) 368 369 def __eq__(self, other): 370 return isinstance(other, Guest) and self.character_id == other.character_id and self.corporation_id == other.corporation_id and self.alliance_id == other.alliance_id and self.war_faction_id == other.war_faction_id 371 372 def __ne__(self, other): 373 return not self.__eq__(other) 374 375 def __hash__(self): 376 return self.character_id 377 378 379 def get_guest_list_settings(): 380 menu = MenuData() 381 menu.AddCheckbox(text=localization.GetByLabel('UI/Chat/ShowCompactMemberList'), setting=guest_list_compact_mode_setting) 382 return menu 383 384 385 def get_guest_entry_class(): 386 if guest_list_compact_mode_setting.is_enabled(): 387 return UserSimple 388 else: 389 return User 390 391 392 def bisect_left(a, x, key): 393 lo = 0 394 hi = len(a) 395 while lo < hi: 396 mid = (lo + hi) // 2 397 if key(a[mid]) < x: 398 lo = mid + 1 399 else: 400 hi = mid 401 402 return lo