message_list_entry.py (12478B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\chat\client\message_list_entry.py 2 import logging 3 import re 4 import sys 5 import types 6 import blue 7 import evelink.client 8 import evetypes 9 import eveicon 10 from carbonui import Align, TextColor 11 import carbonui.const as uiconst 12 from carbonui.primitives.fill import Fill 13 from chatutil.filter import CleanText 14 from eve.client.script.ui import eveColor 15 from menu import MenuLabel 16 from carbon.common.script.util.commonutils import StripTags 17 from carbon.common.script.util.format import GetTimeParts 18 from carbonui.control.scrollentries import SE_BaseClassCore 19 from carbonui.control.link import Link 20 from carbonui.primitives.container import Container 21 from carbonui.primitives.gradientSprite import Gradient2DSprite 22 from carbonui.control.buttonIcon import ButtonIcon 23 from chatutil import LinkURLs 24 from eve.client.script.ui.control.eveIcon import Icon 25 from eve.client.script.ui.control.eveLabel import Label 26 from eve.common.script.sys.idCheckers import IsCharacter 27 from eve.common.lib import appConst as const 28 from eveservices.menu import StartMenuService, GetMenuService 29 from chat.client.const import SLASH_EMOTE_STRING, ChatMessageMode 30 logger = logging.getLogger(__name__) 31 PICTURE_SIZE = {ChatMessageMode.TEXT_ONLY: 0, 32 ChatMessageMode.SMALL_PORTRAIT: 32, 33 ChatMessageMode.LARGE_PORTRAIT: 64} 34 35 class ChatMessageListEntry(SE_BaseClassCore): 36 default_clipChildren = True 37 defaultTextProps = {'autoDetectCharset': True, 38 'linkStyle': uiconst.LINKSTYLE_REGULAR, 39 'state': uiconst.UI_NORMAL, 40 'align': uiconst.TOTOP, 41 'padRight': 4, 42 'padLeft': 4} 43 44 def ApplyAttributes(self, attributes): 45 self._highlight_background = None 46 super(ChatMessageListEntry, self).ApplyAttributes(attributes) 47 48 def Startup(self, *args): 49 self.text = None 50 self.backgroundGradient = None 51 self._expander = None 52 self.picParent = Container(parent=self, align=uiconst.TOPLEFT, width=32, height=32) 53 self.pic = Icon(parent=self.picParent, align=uiconst.TOALL) 54 55 def Load(self, node): 56 self.picloaded = 0 57 textProps = self.GetTextProperties(node) 58 if not self.text: 59 mouseOverWordCallback = node.mouseOverWordCallback 60 collectWordsInStack = node.collectWordsInStack 61 self.text = Label(parent=self, idx=0, mouseOverWordCallback=mouseOverWordCallback, collectWordsInStack=collectWordsInStack, **textProps) 62 self.text.GetMenu = self.GetMenu 63 charname, text = ChatMessageListEntry.GetCharacterAndTextForNode(node) 64 self.text.text = text 65 if node.mode and charname: 66 self.picParent.width = self.picParent.height = PICTURE_SIZE[node.mode] 67 self.picParent.state = uiconst.UI_NORMAL 68 self.LoadPortrait() 69 else: 70 self.picParent.state = uiconst.UI_HIDDEN 71 self.AddEntryBackground(node.charid) 72 73 @staticmethod 74 def GetCharacterAndTextForNode(node): 75 if node.sender is None or type(node.sender) in types.StringTypes: 76 return (None, node.text) 77 nodetext = LinkURLs(node.text) 78 try: 79 charname = cfg.eveowners.Get(node.sender).name 80 except ValueError: 81 charname = None 82 83 if nodetext.startswith('/slash'): 84 color = '0xb200dddd' 85 nodetext = nodetext[1:] 86 showCharname = False 87 else: 88 color = node.color 89 showCharname = True 90 isEmote = False 91 if nodetext.startswith(SLASH_EMOTE_STRING): 92 nodetext = nodetext.replace(SLASH_EMOTE_STRING, '', 1) 93 isEmote = True 94 timestr = '' 95 if settings.user.ui.Get('timestampchat', 0): 96 year, month, wd, day, hour, min, sec, ms = GetTimeParts(node.timestamp) 97 timestr = '<color=%s>[%02d:%02d:%02d]</color> ' % (color, 98 hour, 99 min, 100 sec) 101 if charname: 102 if node.sender == const.ownerSystem: 103 info = 'showinfo:5//%s' % (session.solarsystemid or session.solarsystemid2) 104 else: 105 info = 'showinfo:1373//%d' % node.charid 106 if showCharname: 107 linkStyle = uiconst.LINKSTYLE_UNBOLD 108 if isEmote: 109 prefix = '<url:%s linkStyle=%s><color=%s>* %s</color></url>' % (info, 110 linkStyle, 111 color, 112 charname) 113 else: 114 prefix = '<url:%s linkStyle=%s><color=%s>%s</color></url> > ' % (info, 115 linkStyle, 116 color, 117 charname) 118 else: 119 prefix = '<url:%s/></url>' % info 120 else: 121 prefix = '<color=%s>%s</color> > ' % (color, node.sender) 122 if node.undelivered: 123 color = '0xb2505050' 124 nodetext += ' - undelivered' 125 text = '%s<color=%s>%s%s</color>' % (timestr, 126 color, 127 prefix, 128 nodetext) 129 return (charname, text) 130 131 def LoadHighlightedText(self, text, *args): 132 if self.sr.node.text.lower().find(text.lower()) >= 0: 133 self.isHighlighted = True 134 strippedText = StripTags(self.text.text) 135 pattern = re.compile(text, re.IGNORECASE) 136 textWithEffect = '<b><color=red>' + text + '</color></b>' 137 newText = pattern.sub(textWithEffect, strippedText) 138 self.text.text = newText 139 140 def RemoveHighlightedText(self, *args): 141 if getattr(self, 'isHighlighted', False): 142 self.Load(self.sr.node) 143 144 def LoadPortrait(self, orderIfMissing = True): 145 if self is None or self.destroyed: 146 return 147 if self.sr.node.charid == const.ownerSystem: 148 self.pic.LoadIcon('ui_6_64_7') 149 return 150 size = PICTURE_SIZE[self.sr.node.mode] 151 if sm.GetService('photo').GetPortrait(self.sr.node.charid, size, self.pic, orderIfMissing, callback=True): 152 self.picloaded = 1 153 154 @staticmethod 155 def GetDynamicHeight(node, width): 156 props = ChatMessageListEntry.GetTextProperties(node) 157 width = width - props['padLeft'] - props['padRight'] 158 maxLines = getattr(node, 'maxLines', None) 159 _, text = ChatMessageListEntry.GetCharacterAndTextForNode(node) 160 _, textHeight = Label.MeasureTextSize(text, width=width, **props) 161 if maxLines: 162 maxText = '\n'.join((str(x) for x in range(maxLines))) 163 _, maxTextHeight = Label.MeasureTextSize(maxText, width=width, **props) 164 if textHeight > maxTextHeight: 165 if maxLines == 2: 166 textHeight = maxTextHeight 167 else: 168 textHeight = Label.MeasureTextSize('\n.', width=width, **props)[1] 169 node.showExpander = True 170 else: 171 node.showExpander = False 172 if node.mode == ChatMessageMode.TEXT_ONLY: 173 return textHeight 174 elif node.mode == ChatMessageMode.SMALL_PORTRAIT: 175 return max(PICTURE_SIZE[node.mode], textHeight + props['padTop'] * 2) 176 else: 177 return max(PICTURE_SIZE[node.mode], textHeight + props['padTop'] * 2) 178 179 def GetMenu(self): 180 m = [] 181 mouseOverUrl = self.text.GetMouseOverUrl() 182 characterMenuAdded = False 183 if mouseOverUrl: 184 try: 185 parsed = evelink.parse_show_info_url(mouseOverUrl) 186 except Exception: 187 logger.debug('failed to parse show info link %s', mouseOverUrl, exc_info=True) 188 sys.exc_clear() 189 m = Link().GetLinkMenu(self.text, mouseOverUrl.replace('&', '&')) 190 else: 191 if parsed.item_id: 192 m = StartMenuService().GetMenuFromItemIDTypeID(parsed.item_id, parsed.type_id, includeMarketDetails=True) 193 if evetypes.GetGroupID(parsed.type_id) == const.groupCharacter: 194 characterMenuAdded = True 195 if not parsed.item_id != session.charid: 196 m += [None, (MenuLabel('UI/Chat/ReportIskSpammer'), lambda *args: sm.GetService('chat').report_isk_spammer(parsed.item_id, self.sr.node.channelid))] 197 else: 198 m = Link().GetLinkMenu(self.text, mouseOverUrl.replace('&', '&')) 199 m += [None, (MenuLabel('UI/Common/Copy'), self.CopyText)] 200 if self.sr.node.channelMenu: 201 m += self.sr.node.channelMenu() 202 senderID = self.sr.node.sender 203 if not characterMenuAdded and senderID and IsCharacter(senderID): 204 m += [None, (MenuLabel('UI/Common/Pilot', {'character': senderID}), ('isDynamic', GetMenuService().CharacterMenu, (senderID,)))] 205 return m 206 207 def CopyText(self): 208 node = self.sr.node 209 timestr = '' 210 if settings.user.ui.Get('timestampchat', 0): 211 year, month, wd, day, hour, min, sec, ms = GetTimeParts(node.timestamp) 212 timestr = '[%02d:%02d:%02d] ' % (hour, min, sec) 213 who = node.sender 214 try: 215 who = cfg.eveowners.Get(who).name 216 except ValueError: 217 pass 218 219 text = node.text.replace('>', '>').replace('&', '&') 220 text = CleanText(text) 221 t = '%s%s > %s\r\n' % (timestr, who, text) 222 blue.pyos.SetClipboardData(t) 223 224 def AddEntryBackground(self, charid): 225 if charid == session.charid and settings.user.ui.Get('myMsgHighlighted', 0): 226 if self._highlight_background is None: 227 self._highlight_background = Fill(bgParent=self, color=eveColor.CRYO_BLUE[:3] + (0.1,), idx=0) 228 229 def AddBackgroundGradient(self, color): 230 self.backgroundGradient = Gradient2DSprite(bgParent=self, idx=0, name='gradient2d', rgbHorizontal=[0, 1], rgbVertical=[0, 1], rgbDataHorizontal=[color, color], rgbDataVertical=[color, color], rgbInterp='bezier', alphaHorizontal=[0, 1], alphaDataHorizontal=[1.0, 0.2], alphaVertical=[0, 0.5, 1], alphaDataVertical=[0.35, 0.05, 0.0], textureSize=16) 231 return self.backgroundGradient 232 233 def AddFrameGradient(self, color): 234 frameGradient = Gradient2DSprite(parent=None, idx=0, name='frameGradient2d', rgbHorizontal=[0, 1], rgbVertical=[0, 1], rgbDataHorizontal=[color, color], rgbDataVertical=[color, color], rgbInterp='linear', alphaHorizontal=[0, 0.5, 1], alphaDataHorizontal=[1.0, 0.8, 0.15], alphaVertical=[0, 0.5, 1], alphaDataVertical=[1.0, 0.8, 0.15], textureSize=16) 235 self.picParent.background.insert(0, frameGradient) 236 237 def _OnSizeChange_NoBlock(self, width, height): 238 super(ChatMessageListEntry, self)._OnSizeChange_NoBlock(width, height) 239 self._UpdateExpander() 240 241 def _UpdateExpander(self): 242 if self.sr.node.showExpander: 243 if self._expander is None: 244 self._expander = ButtonIcon(parent=self, align=Align.TORIGHT, idx=0, texturePath=eveicon.expand, height=32, width=32, iconSize=16, iconColor=TextColor.SECONDARY, func=self._ExpandEntry) 245 elif self._expander is not None: 246 self._expander.Close() 247 self._expander = None 248 249 def _ExpandEntry(self, *args, **kwargs): 250 if self.sr.node.maxLines: 251 self.sr.node.maxLines = None 252 self.sr.node.showExpander = False 253 if self._expander is not None: 254 self._expander.Close() 255 self._expander = None 256 self.sr.node.scroll.UpdateNodesWidthAndPosition(forceUpdate=True) 257 self.sr.node.scroll.UpdatePosition() 258 259 @classmethod 260 def GetTextProperties(cls, node): 261 textProps = cls.defaultTextProps 262 textProps['fontsize'] = node.fontsize 263 textProps['letterspace'] = node.letterspace 264 if node.mode == ChatMessageMode.TEXT_ONLY: 265 textProps['padTop'] = 0 266 textProps['padLeft'] = 0 267 textProps['specialIndent'] = 8 268 else: 269 textProps['padTop'] = 4 270 textProps['specialIndent'] = 0 271 portrait_size = PICTURE_SIZE[node.mode] 272 if node.mode == ChatMessageMode.SMALL_PORTRAIT: 273 textProps['padLeft'] = portrait_size + 4 274 else: 275 textProps['padLeft'] = portrait_size + 4 276 return textProps 277 278 @classmethod 279 def MarkMessageUndelivered(cls, node): 280 node.undelivered = True 281 if node.panel: 282 node.panel.Load(node)