window.py (39534B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\chat\client\window.py 2 import blue 3 from bannedwords.client import bannedwords 4 import gametime 5 from carbon.common.script.util.format import GetTimeParts 6 from inventorycommon.const import ownerSystem 7 import threadutils 8 import uthread2 9 import logging 10 from carbon.common.script.sys.serviceConst import ROLE_LEGIONEER, ROLE_CENTURION, ROLEMASK_ELEVATEDPLAYER, ROLE_GML 11 import carbonui 12 from carbonui import Align, PickState, AxisAlignment 13 from carbonui.uianimations import animations 14 from carbonui.uicore import uicore 15 from carbonui.control.window import Window 16 from clientsettings.setting import UserSettingBool, UserSettingEnum 17 from carbonui.primitives.container import Container 18 from carbonui.primitives.containerAutoSize import ContainerAutoSize 19 from carbonui.primitives.fill import Fill 20 from carbonui.primitives.sprite import Sprite 21 from carbonui.decorative.divider_line import DividerLine 22 from carbonui.control.basicDynamicScroll import BasicDynamicScroll 23 from carbonui.control.scrollentries import ScrollEntryNode 24 from carbonui.text.settings import get_font_size_for_preset 25 from carbonui.fontconst import EVE_SMALL_FONTSIZE 26 from carbonui.control.contextMenu.menuData import MenuData 27 from carbonui.control.contextMenu.menuDataFactory import CreateMenuDataFromRawTuples 28 from carbonui.window.settings import GetRegisteredState 29 from eve.client.script.ui.control.divider import Divider 30 from eve.client.script.ui.control.eveEditPlainText import EditPlainText 31 from eve.client.script.ui.control.eveLabel import EveLabelSmall 32 from eve.client.script.ui.util.linkUtil import GetCharIDFromTextLink 33 from eve.client.script.ui.control.allUserEntries import AllUserEntries 34 from eve.client.script.ui.control.entries.util import GetFromClass 35 from eve.client.script.ui.shared.comtool.filterSettings import ChatFilterSettings 36 from eve.common.script.sys.idCheckers import IsEvePlayerCharacter 37 import eveicon 38 from menu import MenuLabel 39 import localization 40 from chatutil.filter import CleanText, ShouldHightlightBlink 41 from chat.client.chat_settings import default_light_background_setting, default_message_mode_setting, default_compact_member_entries_setting, default_show_member_list_setting, default_font_size_setting, global_font_size_setting, elevated_chat_highlighting_setting, show_message_timestamp_setting, highlight_my_messages_setting, ApplyFontSizeGloballySetting, EffectiveFontSizeSetting, auto_collapse_messages, auto_collapse_message_lines 42 from chat.client.const import AVAILABLE_FONT_SIZES, MAX_MESSAGE_LENGTH, MAX_MESSAGES_IN_HISTORY, SLASH_EMOTE_STRING, SLASH_ME_STRING, CHAT_MESSAGE_MODE_LABEL_PATH 43 from chat.client.util import get_chat_default_height, get_chat_default_width, parse_pasted_text, parse_chat_message, truncate_chat_message, check_banned_message_in_new_player_channel 44 from chat.client.window_stack import ChatWindowStack 45 from chat.client.message_list_entry import ChatMessageListEntry 46 from chat.client.member_list_entry import ChatMemberListEntry, ChatMemberCompactListEntry 47 from chat.client.slash_command import handle_slash_command 48 from chat.client.hide_messages import open_hide_message_settings, has_hide_message_setting 49 logger = logging.getLogger('chat') 50 51 class BaseChatWindow(Window): 52 default_caption = 'Chat' 53 default_windowID = 'ChatWindow' 54 default_stackID = 'ChatWindowStack' 55 default_iconNum = 'res:/ui/Texture/WindowIcons/chatchannel.png' 56 default_left = 16 57 default_isKillable = False 58 default_minSize = (120, 120) 59 default_isCompact = True 60 default_apply_content_padding = False 61 62 @staticmethod 63 def default_top(): 64 return uicore.desktop.height - ChatWindow.default_height() - 16 65 66 @staticmethod 67 def default_width(): 68 return get_chat_default_width() 69 70 @staticmethod 71 def default_height(): 72 return get_chat_default_height() 73 74 @staticmethod 75 def default_isLightBackground(): 76 return default_light_background_setting.get() 77 78 @classmethod 79 def should_open_stacked_window_as_minimized(cls, window_id): 80 stack_id = cls.GetRegisteredOrDefaultStackID(windowID=window_id) 81 return GetRegisteredState(stack_id, 'minimized') 82 83 def GetStackClass(self): 84 return ChatWindowStack 85 86 def PostApplyAttributes(self, attributes): 87 attributes['openMinimized'] = self.should_open_stacked_window_as_minimized(attributes.windowID) if attributes.get('check_open_minimized', False) else False 88 Window.PostApplyAttributes(self, attributes) 89 90 91 class ChatWindow(BaseChatWindow): 92 __notifyevents__ = ['OnPortraitCreated'] 93 _controller = None 94 _member_list_entries = {} 95 _pending_message_entries = {} 96 _pending_portrait_reloads = set() 97 _previous_inputs = [''] 98 _previous_input_index = None 99 _highlighted_text = '' 100 _member_update_lock = None 101 _message_update_lock = None 102 103 def ApplyAttributes(self, attributes): 104 self._controller = attributes.controller 105 self._member_update_lock = uthread2.Semaphore() 106 self._message_update_lock = uthread2.Semaphore() 107 super(ChatWindow, self).ApplyAttributes(attributes) 108 self._setup_settings() 109 self._layout() 110 self._register() 111 uthread2.start_tasklet(self._update_member_count) 112 uthread2.start_tasklet(self._setup_members_list, force_reload=True) 113 uthread2.start_tasklet(self._reload_messages) 114 115 def CloseByUser(self, *args): 116 if self.closing and not self.killable: 117 return 118 super(ChatWindow, self).CloseByUser(*args) 119 self._controller.close_by_user() 120 121 def Close(self, *args, **kwargs): 122 if self.closing: 123 return 124 self.unregister() 125 if sm.GetService('focus').GetFocusChannel() == self: 126 sm.GetService('focus').SetFocusChannel() 127 self._controller = None 128 super(ChatWindow, self).Close(*args, **kwargs) 129 130 def _register(self): 131 sm.RegisterNotify(self) 132 self._controller.on_members_set.connect(self._on_members_set) 133 self._controller.on_members_cleared.connect(self._on_members_cleared) 134 self._controller.on_member_added.connect(self._on_member_added) 135 self._controller.on_member_removed.connect(self._on_member_removed) 136 self._controller.on_member_data_changed.connect(self._on_member_data_changed) 137 self._controller.on_message_added.connect(self._on_message_added) 138 self._controller.on_messages_cleared.connect(self._on_messages_cleared) 139 self._controller.on_pending_message_removed.connect(self._on_pending_message_removed) 140 141 def unregister(self): 142 sm.UnregisterNotify(self) 143 if self._controller: 144 self._controller.on_members_set.disconnect(self._on_members_set) 145 self._controller.on_members_cleared.disconnect(self._on_members_cleared) 146 self._controller.on_member_added.disconnect(self._on_member_added) 147 self._controller.on_member_removed.disconnect(self._on_member_removed) 148 self._controller.on_member_data_changed.disconnect(self._on_member_data_changed) 149 self._controller.on_message_added.disconnect(self._on_message_added) 150 self._controller.on_messages_cleared.disconnect(self._on_messages_cleared) 151 self._controller.on_pending_message_removed.disconnect(self._on_pending_message_removed) 152 global_font_size_setting.on_change.disconnect(self._on_global_font_size_setting_changed) 153 elevated_chat_highlighting_setting.on_change.disconnect(self._on_elevated_chat_highlighting_setting_changed) 154 show_message_timestamp_setting.on_change.disconnect(self._on_show_message_timestamp_setting_changed) 155 156 def _on_members_set(self): 157 self._on_members_cleared() 158 self._update_members() 159 160 def _on_members_cleared(self): 161 if not self._controller: 162 return 163 self._update_member_count() 164 self._member_list_entries.clear() 165 self._member_list_scroll.Clear() 166 167 def _on_member_added(self, character_id): 168 self._update_members() 169 170 def _on_member_removed(self, character_id): 171 self._update_members() 172 173 @threadutils.threaded 174 def _on_member_data_changed(self, character_id, data): 175 entry = self._member_list_entries.get(character_id, None) 176 if entry: 177 entry.corpID = data.get('corporation_id', None) 178 entry.allianceID = data.get('alliance_id', None) 179 entry.warFactionID = data.get('war_faction_id', None) 180 if entry.panel: 181 entry.panel.UpdateRelationship(character_id) 182 183 def _on_messages_cleared(self): 184 with self._message_update_lock: 185 if not self._controller: 186 return 187 self._pending_message_entries.clear() 188 self._messages_scroll.Clear() 189 190 def _on_message_added(self, sender_id, text, timestamp, color): 191 if sender_id == session.charid: 192 return 193 with self._message_update_lock: 194 if not self._controller: 195 return 196 self._construct_new_message_entry(sender_id, text, timestamp, color) 197 198 def _on_pending_message_removed(self, message_id, was_delivered): 199 if not self._controller: 200 return 201 message_entry = self._pending_message_entries.pop(message_id, None) 202 if message_entry and not was_delivered: 203 ChatMessageListEntry.MarkMessageUndelivered(message_entry) 204 205 @threadutils.throttled(0.2) 206 def _reload_messages(self): 207 with self._message_update_lock: 208 if not self._controller: 209 return 210 portion = self._messages_scroll.GetScrollProportion() 211 self._messages_scroll.Clear() 212 nodes = [] 213 for sender, text, timestamp, color in self._controller.messages: 214 node = self._construct_message_entry(sender, text, timestamp, color) 215 nodes.append(node) 216 217 self._messages_scroll.AddNodes(-1, nodes) 218 if portion: 219 self._messages_scroll.ScrollToProportion(portion) 220 221 def _construct_new_message_entry(self, sender_id, text, timestamp, color): 222 message_nodes = self._messages_scroll.GetNodes() 223 if message_nodes and len(message_nodes) >= MAX_MESSAGES_IN_HISTORY: 224 self._messages_scroll.RemoveNodes([message_nodes[0]]) 225 entry = self._construct_message_entry(sender_id, text, timestamp, color) 226 self._messages_scroll.AddNodes(-1, [entry]) 227 self._blink_channel_if_needed(sender_id, text) 228 return entry 229 230 def _construct_message_entry(self, sender_id, text, timestamp, color): 231 try: 232 character_id = int(sender_id) 233 except: 234 character_id = sender_id 235 236 if elevated_chat_highlighting_setting.is_enabled(): 237 highlight_func = self._highlight_message 238 collect_words_in_stack = True 239 else: 240 highlight_func = None 241 collect_words_in_stack = False 242 if auto_collapse_messages.get() and character_id and character_id != session.charid and IsEvePlayerCharacter(character_id): 243 maxLines = int(auto_collapse_message_lines.get()) 244 else: 245 maxLines = None 246 node = ScrollEntryNode(decoClass=ChatMessageListEntry, text=text, timestamp=timestamp, fontsize=self._get_font_size(), letterspace=self._get_letter_space(), sender=sender_id, charid=character_id, mode=self._message_style_setting.get(), color=color, channelMenu=self._get_channel_context_menu, mouseOverWordCallback=highlight_func, collectWordsInStack=collect_words_in_stack, channelid=self._controller.channel_id, maxLines=maxLines, showExpander=False) 247 return node 248 249 @threadutils.throttled(0.2) 250 def _reload_members(self): 251 with self._member_update_lock: 252 if not self._controller: 253 return 254 self._member_list_scroll.Clear() 255 self._member_list_entries.clear() 256 entries = [] 257 member_ids = self._controller.member_ids.copy() 258 if member_ids: 259 cfg.eveowners.Prime(member_ids) 260 for character_id in member_ids: 261 entry = self._construct_member_list_entry(character_id) 262 if not entry: 263 continue 264 entries.append(entry) 265 266 sorted_entries = sorted(entries, key=lambda x: x.charIndex) 267 self._member_list_scroll.AddNodes(-1, sorted_entries) 268 269 @threadutils.throttled(0.2) 270 def _update_members(self): 271 self._update_member_count() 272 with self._member_update_lock: 273 if not self._controller: 274 return 275 current_rendered_ids = set(self._member_list_entries.keys()) 276 removed_character_ids = current_rendered_ids - self._controller.member_ids 277 added_character_ids = self._controller.member_ids - current_rendered_ids 278 self._remove_members(removed_character_ids) 279 if added_character_ids: 280 cfg.eveowners.Prime(added_character_ids) 281 self._add_members(added_character_ids) 282 283 def _remove_members(self, character_ids): 284 entries = [] 285 for character_id in character_ids: 286 entry = self._member_list_entries.pop(character_id, None) 287 if entry: 288 entries.append(entry) 289 290 if entries: 291 self._member_list_scroll.RemoveNodes(entries) 292 293 def _add_members(self, character_ids): 294 for character_id in character_ids: 295 self._construct_new_member_list_entry(character_id) 296 297 def _construct_new_member_list_entry(self, character_id): 298 if self.destroyed or character_id in self._member_list_entries or character_id not in self._controller.member_ids: 299 return 300 entry = self._construct_member_list_entry(character_id) 301 if not entry: 302 return 303 idx = GetIdxForUser_WithBinarySearch(self._member_list_scroll, entry.charIndex) 304 self._member_list_scroll.AddNodes(idx, [entry], updateScroll=False) 305 entry.positionFromTop = idx * entry.height 306 self._update_member_list_scroll_position() 307 308 def _construct_member_list_entry(self, character_id): 309 owner_info = cfg.eveowners.GetIfExists(character_id) 310 if not owner_info: 311 logger.warning('_construct_member_list_entry not constructing for character_id %s as it was not in cfg.eveowners', character_id) 312 return 313 if self._compact_member_entries_setting.is_enabled(): 314 entry_type = ChatMemberCompactListEntry 315 else: 316 entry_type = ChatMemberListEntry 317 member_data = self._controller.get_member_data(character_id) 318 entry = GetFromClass(entry_type, {'channelID': self._controller.channel_id, 319 'charID': character_id, 320 'info': owner_info, 321 'corpID': member_data.get('corporation_id', None), 322 'allianceID': member_data.get('alliance_id', None), 323 'warFactionID': member_data.get('war_faction_id', None), 324 'showChatBubble': not self._compact_member_entries_setting.is_enabled(), 325 'charIndex': owner_info.name.lower()}) 326 self._member_list_entries[character_id] = entry 327 return entry 328 329 @threadutils.throttled(0.2) 330 def _update_member_count(self): 331 if not self._controller: 332 return 333 member_count = self._controller.member_count 334 if member_count > 1: 335 self._num_members_label.text = str(member_count) 336 self._num_members_container.display = True 337 self.caption = u'{} [{}]'.format(self._controller.window_display_name, member_count) 338 else: 339 self._num_members_container.display = False 340 self.caption = self._controller.window_display_name 341 342 def _update_min_size(self): 343 if self._member_list_container is None: 344 return 345 default_min_width, default_min_height = self.default_minSize 346 user_list_width, _ = self.GetWindowSizeForContentSize(width=self._member_list_container.width) 347 min_width = max(default_min_width, user_list_width) 348 self.SetMinSize((min_width, default_min_height)) 349 350 def _handle_input_on_return(self): 351 text = parse_chat_message(self._input_field.GetValue(html=0), truncate=False) 352 if not text: 353 return 354 bannedwords.check_chat_character_allowed(session.charid) 355 bannedwords.check_chat_words_allowed(text) 356 self._input_field.SetValue('') 357 if self._previous_inputs[-1] != text: 358 self._previous_inputs.append(text) 359 self._previous_input_index = None 360 if text.startswith(SLASH_ME_STRING): 361 text = text.replace(SLASH_ME_STRING, SLASH_EMOTE_STRING, 1) 362 if text.startswith('/') and not (text.startswith(SLASH_EMOTE_STRING) or text == '/'): 363 handle_slash_command(text, channel_controller=self._controller) 364 return 365 text = truncate_chat_message(text) 366 if text == '': 367 return 368 if not self._controller.can_send_chat_message(): 369 return 370 check_banned_message_in_new_player_channel(self._controller.category_id, text) 371 timestamp = gametime.GetWallclockTime() 372 color = self._controller.get_color_for_sender(session.charid) 373 entry = self._construct_new_message_entry(session.charid, text, timestamp, color) 374 message_id = self._controller.send_chat_message(text) 375 if message_id: 376 self._pending_message_entries[message_id] = entry 377 378 @threadutils.throttled(0.5) 379 def _update_member_list_scroll_position(self): 380 if not self._controller: 381 return 382 width, _ = self._member_list_scroll.sr.clipper.GetAbsoluteSize() 383 with self._member_list_scroll.KillUpdateThreadAndBlock(): 384 self._member_list_scroll.UpdateNodesWidthAndPosition(width) 385 self._member_list_scroll.UpdatePosition(fromWhere='AddNodes') 386 387 def _setup_settings(self): 388 self._message_style_setting = UserSettingEnum(settings_key='%s_mode' % self.name, default_value=lambda : default_message_mode_setting.get(), options=default_message_mode_setting.options) 389 self._font_size_setting = UserSettingEnum(settings_key='chatfontsize_%s' % self.name, default_value=lambda : default_font_size_setting.get(), options=AVAILABLE_FONT_SIZES) 390 self._apply_font_size_globally_setting = ApplyFontSizeGloballySetting(font_size_setting=self._font_size_setting) 391 self._effective_font_size_setting = EffectiveFontSizeSetting(font_size_setting=self._font_size_setting) 392 self._chat_notifications_enabled_setting = UserSettingBool(settings_key='chatWindowBlink_%s' % self.name, default_value=True) 393 self._show_member_list_setting = UserSettingBool(settings_key='%s_usermode' % self.name, default_value=lambda : default_show_member_list_setting.get()) 394 self._compact_member_entries_setting = UserSettingBool(settings_key='chatCondensedUserList_%s' % self.name, default_value=lambda : default_compact_member_entries_setting.get()) 395 self._message_style_setting.on_change.connect(self._on_message_style_setting_changed) 396 self._font_size_setting.on_change.connect(self._on_font_size_setting_changed) 397 self._apply_font_size_globally_setting.on_change.connect(self._on_apply_font_size_globally_setting_changed) 398 self._show_member_list_setting.on_change.connect(self._on_show_member_list_setting_changed) 399 self._compact_member_entries_setting.on_change.connect(self._on_compact_member_entries_setting_changed) 400 global_font_size_setting.on_change.connect(self._on_global_font_size_setting_changed) 401 elevated_chat_highlighting_setting.on_change.connect(self._on_elevated_chat_highlighting_setting_changed) 402 show_message_timestamp_setting.on_change.connect(self._on_show_message_timestamp_setting_changed) 403 404 def _layout(self): 405 self._construct_input() 406 self._construct_user_list() 407 self._construct_message_area() 408 self._update_min_size() 409 self.killable = self._controller.is_closable 410 if elevated_chat_highlighting_setting.is_enabled(): 411 self._input_field.OnKeyUp = self._check_turn_highlight_off 412 self._messages_scroll.OnKeyUp = self._check_turn_highlight_off 413 414 def _construct_input(self): 415 input_cont = ContainerAutoSize(parent=self.content, align=Align.TOBOTTOM, alignMode=Align.TOBOTTOM, padTop=-3) 416 bottom_divider = Divider(name='bottom_divider', parent=input_cont, align=Align.TOTOP_NOPUSH, pickState=PickState.ON, height=8) 417 self._input_field = EditPlainText(parent=input_cont, align=Align.TOBOTTOM, height=settings.user.ui.Get('chatinputsize_%s' % self.name, 64), innerPadding=(8, 8, 8, 8), padding=(-1, 3, -1, -1), pushContent=True, maxLength=MAX_MESSAGE_LENGTH) 418 self._input_field.ValidatePaste = parse_pasted_text 419 self._input_field.OnReturn = self._handle_input_on_return 420 self._input_field.CtrlUp = self._handle_input_control_up 421 self._input_field.CtrlDown = self._handle_input_control_down 422 self._input_field.RegisterFocus = self._RegisterFocus 423 bottom_divider.Startup(self._input_field, 'height', 'y', 30, 96) 424 bottom_divider.OnSizeChanged = self._on_input_size_changed 425 426 def _construct_user_list(self): 427 self._member_list_container = Container(parent=self.content, align=Align.TORIGHT, width=settings.user.ui.Get('%s_userlistwidth' % self.name, 128), clipChildren=True) 428 DividerLine(parent=self._member_list_container, align=Align.TOLEFT_NOPUSH) 429 self._user_list_underlay = Fill(bgParent=self._member_list_container, align=Align.TOALL, padLeft=1, color=(1.0, 1.0, 1.0, 0.05)) 430 divider = Divider(name='user_list_divider', parent=self._member_list_container, align=Align.TOLEFT_NOPUSH, pickState=PickState.ON, width=8, cross_axis_alignment=AxisAlignment.START) 431 divider.Startup(victim=self._member_list_container, attribute='width', xory='x', minValue=50, maxValue=200) 432 divider.OnSizeChanged = self._user_list_end_scale 433 self._num_members_container = Container(name='num_members_container', parent=self._member_list_container, align=Align.TOTOP, pickState=PickState.ON, height=16, padding=(8, 8, 8, 0), hint=self._controller.member_count_tooltip) 434 self._num_members_container.display = False 435 Sprite(name='num_members_sprite', parent=self._num_members_container, align=Align.CENTERLEFT, pickState=carbonui.PickState.OFF, width=16, height=16, texturePath=eveicon.person, color=carbonui.TextColor.NORMAL) 436 self._num_members_label = EveLabelSmall(name='num_members_label', parent=self._num_members_container, align=Align.CENTERLEFT, left=20, text='') 437 self._member_list_scroll = BasicDynamicScroll(name='user_list_scroll', parent=self._member_list_container, align=Align.TOALL, padding=(8, 8, 8, 0), entry_spacing=self._get_user_list_entry_spacing(), pushContent=True) 438 self._member_list_scroll.isCondensed = self._compact_member_entries_setting.is_enabled() 439 self._member_list_scroll.GetContentContainer().OnDropData = self._on_drop_to_user_list 440 441 def _construct_message_area(self): 442 self._messages_scroll = BasicDynamicScroll(parent=self.content, align=Align.TOALL, innerPadding=(8, 8, 8, 8), entry_spacing=4, pushContent=False, stickToBottom=True) 443 self._messages_scroll.sr.content.GetMenu = self._get_channel_context_menu 444 self._messages_scroll.Load(contentList=[], fixedEntryHeight=18) 445 446 def _setup_members_list(self, force_reload = False): 447 if not self._controller: 448 return 449 if not self._show_member_list_setting.is_enabled(): 450 self._member_list_container.display = False 451 else: 452 minW = 50 453 maxW = 200 454 self._member_list_container.width = min(maxW, max(minW, self._member_list_container.width)) 455 self._member_list_container.display = True 456 condensed = self._compact_member_entries_setting.is_enabled() 457 if force_reload or condensed != getattr(self._member_list_scroll, 'isCondensed', False): 458 self._reload_members() 459 460 def _on_message_style_setting_changed(self, value): 461 if not self._controller: 462 return 463 self._reload_messages() 464 uicore.registry.SetFocus(self) 465 466 def _on_font_size_setting_changed(self, value): 467 if self._apply_font_size_globally_setting.is_enabled(): 468 global_font_size_setting.set(self._font_size_setting.get()) 469 else: 470 self._update_font_size() 471 472 def _on_apply_font_size_globally_setting_changed(self, value): 473 if self._apply_font_size_globally_setting.is_enabled(): 474 global_font_size_setting.set(self._font_size_setting.get()) 475 476 def _on_global_font_size_setting_changed(self, value): 477 self._update_font_size() 478 479 def _on_elevated_chat_highlighting_setting_changed(self, value): 480 if elevated_chat_highlighting_setting.is_enabled(): 481 if self._input_field and not self._input_field.destroyed: 482 self._input_field.OnKeyUp = self._check_turn_highlight_off 483 if self._messages_scroll and not self._messages_scroll.destroyed: 484 self._messages_scroll.OnKeyUp = self._check_turn_highlight_off 485 else: 486 if self._input_field and not self._input_field.destroyed: 487 self._input_field.OnKeyUp = None 488 if self._messages_scroll and not self._messages_scroll.destroyed: 489 self._messages_scroll.OnKeyUp = None 490 self._turn_message_highlight_off() 491 self._reload_messages() 492 493 def _on_show_member_list_setting_changed(self, value): 494 self._setup_members_list() 495 496 def _on_compact_member_entries_setting_changed(self, value): 497 is_compact = self._compact_member_entries_setting.is_enabled() 498 if self._show_member_list_setting.is_enabled(): 499 self._member_list_scroll.entry_spacing = self._get_user_list_entry_spacing() 500 self._reload_members() 501 self._member_list_scroll.isCondensed = is_compact if self._member_list_scroll.display else -1 502 503 def _on_show_message_timestamp_setting_changed(self, value): 504 self._reload_messages() 505 506 def _on_input_size_changed(self): 507 settings.user.ui.Set('chatinputsize_%s' % self.name, self._input_field.height) 508 509 def _update_font_size(self): 510 self._reload_messages() 511 self._input_field.SetDefaultFontSize(self._get_font_size()) 512 513 def _get_font_size(self): 514 font_size = global_font_size_setting.get() 515 if font_size is None: 516 font_size = self._font_size_setting.get() 517 return font_size 518 519 def _get_letter_space(self): 520 font_size = self._get_font_size() 521 small_font_size = get_font_size_for_preset(EVE_SMALL_FONTSIZE) 522 if font_size <= small_font_size: 523 return 1 524 else: 525 return 0 526 527 def _get_user_list_entry_spacing(self): 528 if self._compact_member_entries_setting.is_enabled(): 529 return 1 530 else: 531 return 4 532 533 def _check_turn_highlight_off(self, *args, **kwargs): 534 if elevated_chat_highlighting_setting.is_enabled(): 535 self._turn_message_highlight_off() 536 537 def _highlight_message(self, findText = '', *args): 538 if not self._is_highlighting(): 539 return 540 if len(findText.strip().replace('(', '').replace(')', '')) < 2: 541 return 542 if self._highlighted_text != findText: 543 self._turn_message_highlight_off() 544 self._highlighted_text = findText 545 nodes = self._messages_scroll.GetNodes() 546 for eachNode in nodes: 547 if eachNode.panel: 548 eachNode.panel.LoadHighlightedText(findText) 549 550 def _turn_message_highlight_off(self): 551 _highlighted_text = getattr(self, '_highlighted_text', '') 552 if not _highlighted_text: 553 return 554 for eachNode in self._messages_scroll.GetNodes(): 555 if eachNode.panel: 556 eachNode.panel.RemoveHighlightedText() 557 558 self._highlighted_text = None 559 560 def _is_highlighting(self, *args): 561 return elevated_chat_highlighting_setting.is_enabled() and uicore.uilib.Key(carbonui.uiconst.VK_CONTROL) 562 563 def _blink_channel_if_needed(self, sender_id, text): 564 if sender_id in (session.charid, ownerSystem): 565 return 566 blink_enabled = self._chat_notifications_enabled_setting.is_enabled() 567 if blink_enabled or ShouldHightlightBlink(text): 568 self.Blink() 569 if self.state == carbonui.uiconst.UI_HIDDEN or self.IsMinimized(): 570 self.SetBlinking() 571 572 def _get_channel_context_menu(self, *args): 573 m = [(MenuLabel('UI/Common/CopyAll'), self._copy_all_messages)] 574 return m 575 576 def _copy_all_messages(self): 577 t = '' 578 for node in self._messages_scroll.GetNodes(): 579 timestr = '' 580 if show_message_timestamp_setting.is_enabled(): 581 year, month, wd, day, hour, min, sec, ms = GetTimeParts(node.timestamp) 582 timestr = '[%02d:%02d:%02d] ' % (hour, min, sec) 583 who = node.sender 584 try: 585 who = cfg.eveowners.Get(who).name 586 except ValueError: 587 pass 588 589 text = node.text.replace('>', '>').replace('&', '&') 590 text = CleanText(text) 591 t += '%s%s > %s\r\n' % (timestr, who, text) 592 593 blue.pyos.SetClipboardData(t) 594 595 def _handle_input_control_down(self, editctrl): 596 self._browse_inputs(1) 597 598 def _handle_input_control_up(self, editctrl): 599 self._browse_inputs(-1) 600 601 def _browse_inputs(self, updown): 602 if self._previous_input_index is None: 603 self._previous_input_index = len(self._previous_inputs) - 1 604 else: 605 self._previous_input_index += updown 606 if self._previous_input_index < 0: 607 self._previous_input_index = len(self._previous_inputs) - 1 608 elif self._previous_input_index >= len(self._previous_inputs): 609 self._previous_input_index = 0 610 self._input_field.SetValue(self._previous_inputs[self._previous_input_index], cursorPos=-1) 611 612 def _user_list_end_scale(self, *args): 613 settings.user.ui.Set('%s_userlistwidth' % self.name, self._member_list_container.width) 614 self._update_min_size() 615 616 def _on_drop_to_user_list(self, dragObj, nodes): 617 for node in nodes[:5]: 618 character_id = GetCharIDFromTextLink(node) 619 if not character_id: 620 guid = getattr(node, '__guid__', None) 621 if guid not in AllUserEntries(): 622 return 623 character_id = node.charID 624 if self._controller.can_invite_character(character_id): 625 self._controller.invite_character(character_id) 626 627 def _RegisterFocus(self, *args, **kwargs): 628 sm.GetService('focus').SetFocusChannel(self) 629 630 def SetCharFocus(self, char): 631 uicore.registry.SetFocus(self._input_field) 632 uicore.layer.menu.Flush() 633 if char is not None: 634 self._input_field.OnChar(char, 0) 635 636 def OnTabSelect(self): 637 uicore.registry.SetFocus(self._input_field) 638 self._reload_pending_portaits() 639 640 def OnPortraitCreated(self, charID, _size): 641 if self.destroyed or self._messages_scroll is None: 642 return 643 self._pending_portrait_reloads.add(charID) 644 if not self.display: 645 return 646 self._reload_pending_portaits() 647 648 def _reload_pending_portaits(self): 649 if not self._pending_portrait_reloads: 650 return 651 if self._show_member_list_setting.is_enabled(): 652 for character_id in self._pending_portrait_reloads: 653 entry = self._member_list_entries.get(character_id, None) 654 if entry and entry.panel and not entry.panel.picloaded: 655 entry.panel.LoadPortrait(orderIfMissing=False) 656 657 for entry in self._messages_scroll.GetNodes(): 658 if not entry.panel or not entry.panel.display: 659 continue 660 if entry.charid in self._pending_portrait_reloads and not entry.panel.picloaded: 661 entry.panel.LoadPortrait(orderIfMissing=False) 662 663 self._pending_portrait_reloads.clear() 664 665 def OnSetActive_(self, *args): 666 if self._user_list_underlay is not None: 667 animations.FadeTo(self._user_list_underlay, startVal=self._user_list_underlay.opacity, endVal=0.05, duration=0.1) 668 669 def OnSetInactive(self, *args): 670 super(ChatWindow, self).OnSetInactive() 671 if self._user_list_underlay is not None: 672 animations.FadeOut(self._user_list_underlay, duration=0.3) 673 674 def OnMouseUp(self, _): 675 self.StackSelf() 676 677 @threadutils.threaded 678 def StackSelf(self): 679 if self.stacked: 680 self.stack.Check() 681 elif self.FindWindowToStackTo(): 682 pass 683 else: 684 self._ConstructStack([(self, 1)], []) 685 686 def SetBlinking(self): 687 super(ChatWindow, self).SetBlinking() 688 sm.ScatterEvent('OnChatWindowStartBlinking', self) 689 690 def SetNotBlinking(self): 691 super(ChatWindow, self).SetNotBlinking() 692 sm.ScatterEvent('OnChatWindowStopBlinking', self) 693 694 def GetMenu(self, *args): 695 menu = MenuData() 696 menu.AddCaption(self.caption) 697 menu.AddSeparator() 698 self._add_channel_menu(menu) 699 tab_menu = CreateMenuDataFromRawTuples(super(ChatWindow, self).GetMenu(*args)) 700 if tab_menu is not None and tab_menu.GetEntries(): 701 menu += tab_menu 702 menu.AddSeparator() 703 return menu 704 705 def GetMenuMoreOptions(self): 706 menu = super(ChatWindow, self).GetMenuMoreOptions() 707 self._add_channel_menu(menu) 708 return menu 709 710 def _add_channel_menu(self, menu): 711 gm_options = MenuData() 712 if session.role & ROLE_GML: 713 gm_options.AddEntry(text='channel id: ' + self._controller.channel_id, func=lambda : blue.pyos.SetClipboardData(self._controller.channel_id)) 714 gm_options.AddEntry(text='category id: ' + self._controller.category_id, func=lambda : blue.pyos.SetClipboardData(self._controller.category_id)) 715 gm_options.AddEntry(text='window id: ' + self._controller.window_id, func=lambda : blue.pyos.SetClipboardData(self._controller.window_id)) 716 gm_options.AddSeparator() 717 gm_options.AddEntry(text='Reload Members', func=self._reload_members) 718 gm_options.AddEntry(text='Reload Messages', func=self._reload_messages) 719 gm_options.AddEntry(text='Copy Members Data', func=self._qa_copy_member_data) 720 if gm_options.GetEntries(): 721 menu.AddEntry(text='GM / WM Extras', subMenuData=gm_options) 722 elevated_role_mask = ROLE_CENTURION | ROLE_LEGIONEER | ROLEMASK_ELEVATEDPLAYER 723 if session.role & elevated_role_mask: 724 menu.AddCheckbox(text='Special chat highlighting', setting=elevated_chat_highlighting_setting) 725 menu.AddSeparator() 726 menu.AddLabel(localization.GetByLabel('UI/Chat/ChannelSettingsSection')) 727 member_list_options = MenuData() 728 member_list_options.AddCheckbox(text=localization.GetByLabel('UI/Chat/ShowMemberList'), setting=self._show_member_list_setting) 729 member_list_options.AddSeparator() 730 member_list_options.AddCheckbox(text=localization.GetByLabel('UI/Chat/ShowCompactMemberList'), setting=self._compact_member_entries_setting) 731 menu.AddEntry(text=localization.GetByLabel('UI/Chat/MemberList'), texturePath=eveicon.people, subMenuData=member_list_options) 732 font_size_menu = MenuData() 733 font_size_menu.AddCheckbox(text=localization.GetByLabel('UI/Chat/UseFontSizeInAllChannels'), setting=self._apply_font_size_globally_setting) 734 font_size_menu.AddSeparator() 735 for font_size in self._effective_font_size_setting.options: 736 font_size_menu.AddRadioButton(text=str(font_size), value=font_size, setting=self._effective_font_size_setting) 737 738 menu.AddEntry(text=localization.GetByLabel('/Carbon/UI/Controls/EditRichText/FontSize'), texturePath=eveicon.font_size, subMenuData=font_size_menu) 739 message_style_menu = MenuData() 740 for mode in self._message_style_setting.options: 741 message_style_menu.AddRadioButton(text=localization.GetByLabel(CHAT_MESSAGE_MODE_LABEL_PATH[mode]), value=mode, setting=self._message_style_setting) 742 743 menu.AddEntry(text=localization.GetByLabel('UI/Chat/MessagePortraits'), texturePath=eveicon.person, subMenuData=message_style_menu) 744 if has_hide_message_setting(self._controller.category_id): 745 menu.AddEntry(text=localization.GetByLabel('UI/Chat/MessageFilterContextMenu'), texturePath=eveicon.filter, func=self._open_hide_message_settings) 746 menu.AddCheckbox(text=localization.GetByLabel('UI/Chat/BlinkOnNewActivity'), setting=self._chat_notifications_enabled_setting) 747 if self._controller.motd: 748 menu.AddEntry(text=localization.GetByLabel('UI/Chat/ReloadChannelMOTD'), func=self._show_motd) 749 menu.AddEntry(text=localization.GetByLabel('UI/Chat/ClearAllContent'), func=self._controller.clear_messages) 750 menu.AddSeparator() 751 menu.AddLabel(localization.GetByLabel('UI/Chat/GlobalSettingsSection')) 752 autoCollapseMenu = MenuData() 753 autoCollapseMenu.AddCheckbox(localization.GetByLabel('UI/Chat/AutoCollapseMessagesEnabled'), hint=localization.GetByLabel('UI/Chat/AutoCollapseMessagesHint'), setting=auto_collapse_messages) 754 autoCollapseMenu.AddSlider(localization.GetByLabel('UI/Chat/AutoCollapseMessagesLines'), setting=auto_collapse_message_lines, isInteger=True) 755 menu.AddEntry(text=localization.GetByLabel('UI/Chat/AutoCollapseMessages'), hint=localization.GetByLabel('UI/Chat/AutoCollapseMessagesHint'), texturePath=eveicon.collapse, subMenuData=autoCollapseMenu) 756 menu.AddCheckbox(text=localization.GetByLabel('UI/Chat/HighlightMyMessages'), setting=highlight_my_messages_setting) 757 menu.AddCheckbox(text=localization.GetByLabel('UI/Chat/ShowTimestamp'), setting=show_message_timestamp_setting) 758 menu.AddEntry(text=localization.GetByLabel('UI/Chat/ConfigureWordFiltersAndHighlights'), func=self._set_word_and_highlight_filters) 759 return menu 760 761 def _set_word_and_highlight_filters(self): 762 ChatFilterSettings.Open() 763 764 def _open_hide_message_settings(self): 765 self._controller.open_hide_message_settings() 766 767 def _show_motd(self): 768 motd = self._controller.motd 769 if motd: 770 message = localization.GetByLabel('UI/Chat/ChannelMotd', motd=motd) 771 self._controller.add_game_message(text=message, sender_id=ownerSystem) 772 773 def _qa_copy_member_data(self): 774 import json 775 data = {member_id:self._controller.get_member_data(member_id) for member_id in self._controller.member_ids} 776 blue.pyos.SetClipboardData(json.dumps(data, indent=2, sort_keys=True)) 777 778 779 def GetIdxForUser_WithBinarySearch(member_list_scroll, entry_name): 780 a = member_list_scroll.GetNodes() 781 x = entry_name 782 lo = 0 783 hi = len(a) 784 while lo < hi: 785 mid = (lo + hi) // 2 786 if x < a[mid].charIndex: 787 hi = mid 788 else: 789 lo = mid + 1 790 791 return lo