uilog.py (46132B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\shared\uilog.py 2 import os 3 import sys 4 import blue 5 import eveicon 6 import evetypes 7 import signals 8 import uthread 9 from actionLog.client.formatters import FormatBountyMessage, FormatModifiedBountyMessage, FormatMiningMessage, FormatMiningRewardsMessage, FormatOreDepositedMessage, FormatPirateKilledMessage, FormatMiningCritMessage, FormatMiningWasteMessage 10 from carbon.common.script.sys.service import Service 11 from carbon.common.script.sys.serviceConst import ROLE_SERVICE 12 from carbon.common.script.util.commonutils import StripTags 13 from carbon.common.script.util.format import FmtDate 14 from carbonui import uiconst 15 from carbonui.control.contextMenu.menuData import MenuData 16 from carbonui.control.contextMenu.menuEntryData import MenuEntryDataCheckbox, MenuEntryDataRadioButton 17 from carbonui.control.window import Window 18 from carbonui.primitives.containerAutoSize import ContainerAutoSize 19 from clientsettings.setting import UserSettingBool, UserSettingEnum 20 from carbonui.uicore import uicore 21 from eve.client.script.ui import eveColor 22 from eve.client.script.ui.control.entries.generic import Generic 23 from eve.client.script.ui.control.entries.util import GetFromClass 24 from carbonui.button.menu import MenuButtonIcon 25 from eve.client.script.ui.menuUtil import DESTRUCTIVEGROUP 26 from eve.client.script.ui.util import uix 27 from eve.common.lib import appConst as const 28 from carbon.common.script.util import timerstuff 29 from carbon.common.script.util.logUtil import LogError, LogException, LogInfo, LogWarn 30 from eve.client.script.ui.control import eveScroll 31 import localization 32 import telemetry 33 import trinity 34 import itertoolsext 35 import log 36 from eve.client.script.ui.control.message import CombatMessage 37 from eve.common.script.sys import idCheckers 38 from eveprefs import prefs 39 from localization import GetByLabel 40 from menu import MenuLabel 41 from mining.client.mining_const import MiningQuantityColor 42 MAX_MSGS = 256 43 44 class MessageType(): 45 INFO = 'info' 46 WARNING = 'warning' 47 ERROR = 'error' 48 COMBAT = 'combat' 49 NOTIFY = 'notify' 50 QUESTION = 'question' 51 HINT = 'hint' 52 SLASH = 'slash' 53 MINING = 'mining' 54 BOUNTY = 'bounty' 55 56 57 COLORS_BY_TYPE = {MessageType.ERROR: '0xffeb3700', 58 MessageType.WARNING: '0xffffd800', 59 MessageType.SLASH: '0xffff5500', 60 MessageType.COMBAT: '0xffff0000', 61 MessageType.BOUNTY: '0xff00aa00', 62 MessageType.MINING: MiningQuantityColor.YIELD.hex_argb} 63 ENERGY_WARFARE_MESSAGES = ['OnEnergyNeutDefender', 64 'OnEnergyNeutAttacker', 65 'OnEnergyNosDefender', 66 'OnEnergyNosAttacker'] 67 INCOMING_REMOTE_ASSISTANCE_MESSAGES = ['OnRemoteShieldBoostTarget', 68 'OnRemoteArmorRepairTarget', 69 'OnRemoteHullRepairTarget', 70 'OnRemoteCapacitorTransmitterTarget'] 71 OUTGOING_REMOTE_ASSISTANCE_MESSAGES = ['OnRemoteShieldBoostSource', 72 'OnRemoteArmorRepairSource', 73 'OnRemoteHullRepairSource', 74 'OnRemoteCapacitorTransmitterSource'] 75 REMOTE_ASSISTANCE_MESSAGES = INCOMING_REMOTE_ASSISTANCE_MESSAGES + OUTGOING_REMOTE_ASSISTANCE_MESSAGES 76 DAMAGE_TYPE_ICONS = {const.attributeKineticDamage: 'res:/UI/Texture/classes/DamageTypes/Kinetic.png', 77 const.attributeThermalDamage: 'res:/UI/Texture/classes/DamageTypes/Thermal.png', 78 const.attributeExplosiveDamage: 'res:/UI/Texture/classes/DamageTypes/Explosive.png', 79 const.attributeEmDamage: 'res:/UI/Texture/classes/DamageTypes/Electromagnetic.png'} 80 81 class Logger(Service): 82 __exportedcalls__ = {'AddMessage': [], 83 'AddCombatMessage': [], 84 'AddText': [], 85 'AddBountyMessage': [], 86 'AddMiningMessage': [], 87 'GetLog': [ROLE_SERVICE]} 88 __guid__ = 'svc.logger' 89 __notifyevents__ = ['ProcessSessionChange'] 90 __servicename__ = 'logger' 91 __displayname__ = 'Logger Client Service' 92 __dependencies__ = [] 93 __update_on_reload__ = 0 94 95 def Run(self, memStream = None): 96 self.LogInfo('Starting Logger') 97 self.broken = 0 98 self.Reset() 99 self.newfileAttempts = 0 100 self.addmsg = [] 101 self.persistInterval = 1000 102 self.combatMessagePeriod = 10000 * prefs.GetValue('combatMessagePeriod', 200) 103 self.lastCombatMessage = 0 104 self.inMovingMode = False 105 self.frameCounterAndWindow = (None, None) 106 self.cachedHitQualityText = {} 107 self.notificationFontSize = inflight_message_font_size_setting.get() 108 uthread.new(self._PersistWorker) 109 110 def Stop(self, memStream = None): 111 self.DumpToLog() 112 self.messages = [] 113 self.msglog = [] 114 self.addmsg = [] 115 116 def ProcessSessionChange(self, isremote, session, change): 117 if 'charid' in change: 118 self.Reset() 119 if not session.charid: 120 self.Stop() 121 122 def GetTimePartsForLog(self, timestamp = None): 123 if timestamp is None: 124 timestamp = blue.os.GetWallclockTime() 125 timestamp += localization.GetTimeDeltaSeconds() * const.SEC 126 return blue.os.GetTimeParts(timestamp) 127 128 def GetLog(self, maxsize = const.petitionMaxCombatLogSize, *args): 129 LogInfo('Getting logfiles') 130 self.DumpToLog() 131 year, month, weekday, day, hour, minute, second, msec = self.GetTimePartsForLog() 132 now = '%d%.2d%.2d' % (year, month, day) 133 year, month, weekday, day, hour, minute, second, msec = self.GetTimePartsForLog(blue.os.GetWallclockTime() - const.DAY) 134 yesterday = '%d%.2d%.2d' % (year, month, day) 135 root = blue.sysinfo.GetUserDocumentsDirectory() + '/EVE/logs/Gamelogs' 136 logs = [] 137 for each in os.listdir(root): 138 filename = os.path.join(root, each) 139 fooname = filename[:-11] 140 if fooname.endswith(now) or fooname.endswith(yesterday): 141 logs.append((each, filename)) 142 143 logs.sort(reverse=True) 144 ret = [] 145 bytesread = 0 146 for k, filename in logs: 147 f = None 148 try: 149 f = file(filename) 150 tmp = f.read() 151 f.close() 152 line = '\n\n%s:\n%s' % (filename.encode('utf8', 'replace'), tmp) 153 bytesread += len(line) 154 ret.append(line) 155 if bytesread > maxsize: 156 break 157 except: 158 LogException() 159 sys.exc_clear() 160 if f and not f.closed: 161 f.close() 162 163 LogInfo('Getting logfiles done') 164 ret.reverse() 165 return ''.join(ret)[-maxsize:] 166 167 def Reset(self): 168 self.resettime = blue.os.GetWallclockTime() 169 self.messages = [] 170 self.msglog = ['-' * 60 + '\n', ' %s\n' % GetByLabel('UI/Accessories/Log/GameLog').encode('utf8', 'replace')] 171 if session.charid: 172 self.msglog.append((' %s: %s\n' % (GetByLabel('UI/Accessories/Log/Listener'), cfg.eveowners.Get(session.charid).name)).encode('utf8', 'replace')) 173 self.msglog += [(' %s: %s\n' % (GetByLabel('UI/Accessories/Log/SessionStarted'), FmtDate(self.resettime))).encode('utf8', 'replace'), 174 '', 175 '-' * 60 + '\n', 176 ''] 177 self.DumpToLog() 178 179 @telemetry.ZONE_METHOD 180 def GetWnd(self): 181 return LoggerWindow.GetIfOpen() 182 183 @telemetry.ZONE_METHOD 184 def GetWndWithFrameCounter(self, *args): 185 frameCounter = trinity.GetCurrentFrameCounter() 186 if frameCounter == self.frameCounterAndWindow[0]: 187 return self.frameCounterAndWindow[1] 188 wnd = self.GetWnd() 189 self.frameCounterAndWindow = (frameCounter, wnd) 190 return wnd 191 192 def GetMessages(self): 193 self.addmsg = [] 194 return self.messages 195 196 def ClearAllMessages(self): 197 self.Reset() 198 199 def GetPendingMessages(self): 200 retval = self.addmsg 201 self.addmsg = [] 202 return retval 203 204 def AddMessage(self, msg, msgtype = None): 205 if msg.type == MessageType.ERROR and not settings.user.ui.Get('logerrors', 0): 206 return 207 self.AddText(msg.text, msgtype or msg.type or MessageType.NOTIFY, msg) 208 209 def GetHitQualityMessage(self, hitQuality): 210 try: 211 hitQuality = int(hitQuality) 212 label = {1: 'UI/Inflight/HitQuality1', 213 2: 'UI/Inflight/HitQuality2', 214 3: 'UI/Inflight/HitQuality3', 215 4: 'UI/Inflight/HitQuality4', 216 5: 'UI/Inflight/HitQuality5', 217 6: 'UI/Inflight/HitQuality6'}[hitQuality] 218 except (KeyError, ValueError): 219 return 'No hit quality message for level %s' % hitQuality 220 221 return GetByLabel(label) 222 223 def AddMiningMessage(self, oreTypeID, quantity_mined, has_rewards, quantity_wasted, quantity_crit_bonus): 224 if not show_inflight_mining_messages_setting.is_enabled(): 225 return 226 if quantity_mined > 0: 227 if has_rewards: 228 msg = FormatMiningRewardsMessage(normalFontSize=self.notificationFontSize, oreTypeID=oreTypeID, amount=quantity_mined) 229 else: 230 msg = FormatMiningMessage(normalFontSize=self.notificationFontSize, oreTypeID=oreTypeID, amount=quantity_mined) 231 self.SayMessage(msg, MessageType.MINING) 232 if quantity_wasted: 233 msg = FormatMiningWasteMessage(normalFontSize=self.notificationFontSize, oreTypeID=oreTypeID, amountWasted=quantity_wasted) 234 self.SayMessage(msg, MessageType.MINING) 235 if quantity_crit_bonus: 236 msg = FormatMiningCritMessage(normalFontSize=self.notificationFontSize, oreTypeID=oreTypeID, amount=quantity_crit_bonus) 237 self.SayMessage(msg, MessageType.MINING, icons=[eveicon.crit_success], icon_color=MiningQuantityColor.CRIT_BONUS) 238 239 def AddOreDepositedMessage(self, oreTypeID, amount, charID): 240 if amount > 0: 241 if show_inflight_mining_messages_setting.get(): 242 msg = FormatOreDepositedMessage(charID=charID, normalFontSize=self.notificationFontSize, oreTypeID=oreTypeID, amount=amount) 243 self.SayMessage(msg, MessageType.MINING) 244 245 def AddRwPirateKilledByCharacterMessage(self, killerID, killedShipItem): 246 if show_inflight_damage_messages_setting.get(): 247 killerName = cfg.eveowners.Get(killerID).name 248 killedName = sm.GetService('bracket').GetDisplayNameForBracket(killedShipItem) 249 msg = FormatPirateKilledMessage(killerName=killerName, normalFontSize=self.notificationFontSize, killedName=killedName) 250 self.SayMessage(msg, MessageType.COMBAT) 251 252 def AddRwPirateKilledByNpcMessage(self, killerShipItem, killedShipItem): 253 if show_inflight_damage_messages_setting.get(): 254 killerName = sm.GetService('bracket').GetDisplayNameForBracket(killerShipItem) 255 killedName = sm.GetService('bracket').GetDisplayNameForBracket(killedShipItem) 256 msg = FormatPirateKilledMessage(killerName=killerName, normalFontSize=self.notificationFontSize, killedName=killedName) 257 self.SayMessage(msg, MessageType.COMBAT) 258 259 def AddBountyMessage(self, bountyPayout, payoutTime, enemyTypeID, isModified): 260 if bountyPayout > 0: 261 if show_inflight_bounty_messages_setting.get(): 262 blue.synchro.Sleep(1000) 263 if not isModified: 264 msg = FormatBountyMessage(normalFontSize=self.notificationFontSize, bounty=bountyPayout) 265 self.SayMessage(msg, MessageType.BOUNTY) 266 else: 267 msg = FormatModifiedBountyMessage(normalFontSize=self.notificationFontSize, bounty=bountyPayout) 268 self.SayMessage(msg, MessageType.BOUNTY) 269 270 def _ReadIconsFromMessageDict(self, damageMessagesArgs): 271 icons = [] 272 if damageMessagesArgs.get('hitQuality', 0) == 0: 273 return [] 274 damageAttributes = damageMessagesArgs.get('damageAttributes', {}) 275 for attr in damageAttributes: 276 if attr in DAMAGE_TYPE_ICONS and damageAttributes[attr] > 0: 277 icons.append(DAMAGE_TYPE_ICONS[attr]) 278 279 return icons 280 281 @telemetry.ZONE_METHOD 282 def AddCombatMessageFromDict(self, damageMessagesArgs): 283 hitQuality = damageMessagesArgs['hitQuality'] 284 isBanked = damageMessagesArgs['isBanked'] 285 if hitQuality == 0: 286 msgKey = 'AttackMiss' 287 elif hitQuality > 0 and hitQuality <= 6: 288 msgKey = 'AttackHits' 289 try: 290 hitQualityText = self.cachedHitQualityText[hitQuality] 291 except KeyError: 292 hitQualityText = self.GetHitQualityMessage(hitQuality) 293 self.cachedHitQualityText[hitQuality] = hitQualityText 294 295 damageMessagesArgs['hitQualityText'] = hitQualityText 296 else: 297 msgKey = 'AttackHits' 298 hitQualityText = '' 299 damageMessagesArgs['hitQualityText'] = hitQualityText 300 attackType = damageMessagesArgs.get('attackType', 'me') 301 if attackType == 'otherPlayer': 302 msgKey += 'RD' 303 elif attackType == 'otherPlayerWeapons': 304 msgKey += 'R' 305 elif attackType == 'me': 306 if isBanked: 307 msgKey += 'Banked' 308 else: 309 LogError('attackType not valid! attackType = ' + attackType) 310 return 311 for argName in ('source', 'target', 'owner'): 312 if argName not in damageMessagesArgs: 313 continue 314 if argName == 'owner': 315 ownerTypeID, ownerID = damageMessagesArgs[argName] 316 if ownerTypeID != const.UE_OWNERID: 317 continue 318 objectID = damageMessagesArgs.get('attackerID', None) 319 if objectID is None: 320 continue 321 else: 322 objectID = damageMessagesArgs[argName] 323 bracket = sm.GetService('bracket').GetBracket(objectID) 324 if bracket: 325 slimItem = bracket.slimItem 326 else: 327 ballpark = sm.GetService('michelle').GetBallpark() 328 if ballpark is None: 329 self.LogWarn('OnDamageMessage: No ballpark, not showing damage message.') 330 return 331 slimItem = ballpark.GetInvItem(objectID) 332 if slimItem: 333 if slimItem.corpID and not idCheckers.IsNPC(slimItem.corpID): 334 damageMessagesArgs['typeName'] = evetypes.GetName(slimItem.typeID) 335 tickerText = cfg.corptickernames.Get(slimItem.corpID).tickerName 336 damageMessagesArgs['tickerText'] = tickerText 337 objectName = self._GetObjectName(slimItem, argName, damageMessagesArgs) 338 if objectName is None: 339 if argName == 'owner': 340 continue 341 else: 342 return 343 damageMessagesArgs[argName] = objectName 344 damageMessagesArgs['%s_ID' % argName] = objectID 345 346 self.AddCombatMessage(msgKey, damageMessagesArgs) 347 348 def _GetObjectName(self, slimItem, argName, damageMessagesArgs): 349 if slimItem: 350 return uix.GetSlimItemName(slimItem) or 'Some object' 351 if argName == 'owner': 352 return 353 if argName == 'source' and 'sourceCharID' in damageMessagesArgs: 354 charID = damageMessagesArgs['sourceCharID'] 355 return cfg.eveowners.Get(charID).name 356 if argName == 'target' and 'targetOwnerID' in damageMessagesArgs: 357 targetOwnerID = damageMessagesArgs['targetOwnerID'] 358 return cfg.eveowners.Get(targetOwnerID).name 359 self.LogError('Failed to display message', damageMessagesArgs) 360 361 @telemetry.ZONE_METHOD 362 def AddCombatMessage(self, msgKey, msgTextArgs): 363 if msgKey in ENERGY_WARFARE_MESSAGES and not show_inflight_energy_warfare_messages_setting.get(): 364 return 365 if msgKey in REMOTE_ASSISTANCE_MESSAGES: 366 if not show_inflight_remote_assistance_messages_setting.get(): 367 return 368 if msgKey in INCOMING_REMOTE_ASSISTANCE_MESSAGES and not show_inflight_remote_assistance_incoming_messages_setting.get(): 369 return 370 if msgKey in OUTGOING_REMOTE_ASSISTANCE_MESSAGES and not show_inflight_remote_assistance_outgoing_messages_setting.get(): 371 return 372 icons = self._ReadIconsFromMessageDict(msgTextArgs) 373 smallFontsize = max(10, self.notificationFontSize - 2) 374 msgTextArgs.update({'strongColor': '<color=0xff00ffff>', 375 'faintColor': '<color=0x77ffffff>', 376 'fontMarkUpStart': '<font size=%s>' % smallFontsize, 377 'fontMarkUpEnd': '</font>'}) 378 showTicker = show_inflight_message_ticker_setting.get() 379 showShip = show_inflight_message_ship_setting.get() 380 showWeapon = show_inflight_message_weapon_setting.get() 381 showQuality = show_inflight_message_quality_setting.get() 382 doShowInspaceNotifications = show_inflight_messages_setting.get() 383 notificationsExtraNameText = '' 384 logExtraNameText = '' 385 if msgTextArgs.get('tickerText', ''): 386 extra = '[%s]' % msgTextArgs.get('tickerText', '') 387 logExtraNameText += extra 388 if showTicker: 389 notificationsExtraNameText += extra 390 if msgTextArgs.get('typeName', ''): 391 extra = '(%s)' % msgTextArgs.get('typeName', '') 392 logExtraNameText += extra 393 if showShip: 394 notificationsExtraNameText += extra 395 deflectedDmg = msgTextArgs.get('deflectedDmg', None) 396 if deflectedDmg: 397 damage = msgTextArgs.get('damage', 0) 398 msgTextArgs['damage'] = damage + deflectedDmg 399 msgTextArgs['extraNameText'] = logExtraNameText 400 msgFullText = cfg.GetMessageTypeAndText(msgKey, msgTextArgs) 401 if msgFullText.type == MessageType.ERROR and not settings.user.ui.Get('logerrors', 0): 402 return 403 if doShowInspaceNotifications: 404 if logExtraNameText == notificationsExtraNameText: 405 msgLimited = msgFullText 406 else: 407 msgTextArgs['extraNameText'] = notificationsExtraNameText 408 msgLimited = cfg.GetMessageTypeAndText(msgKey, msgTextArgs) 409 logPostText = '' 410 notificationsPostText = '' 411 if 'weapon' in msgTextArgs: 412 extra = ' - %s' % evetypes.GetName(msgTextArgs['weapon']) 413 logPostText += extra 414 if showWeapon: 415 notificationsPostText += extra 416 if msgTextArgs.get('hitQualityText', ''): 417 extra = ' - %s' % msgTextArgs['hitQualityText'] 418 logPostText += extra 419 if showQuality: 420 notificationsPostText += extra 421 if deflectedDmg: 422 notificationsPostText += ' (%s)' % GetByLabel('UI/Accessories/Log/DeflectedDamage', damageDeflected=deflectedDmg) 423 logPostText += notificationsPostText 424 damage = msgTextArgs.get('damage', None) 425 hitQuality = msgTextArgs.get('hitQuality', None) 426 damageHtml = None 427 if damage is not None and hitQuality > 0: 428 rounded_damage = int(round(damage)) 429 red = '0xffcc0000' 430 blue = '0xff00ffff' 431 color = red if msgTextArgs.get('attackType', 'me') != 'me' else blue 432 damageHtml = '<color={color}><b>{damage}</b>'.format(color=color, damage=rounded_damage) 433 fullText = msgFullText.text + logPostText 434 if damageHtml is not None: 435 fullText = ' '.join((damageHtml, fullText)) 436 self.AddText(fullText, MessageType.COMBAT) 437 if doShowInspaceNotifications: 438 limitedText = msgLimited.text + notificationsPostText 439 hitQuality = msgTextArgs.get('hitQuality', None) 440 attackerID = msgTextArgs.get('attackerID', None) 441 self.Say(limitedText, hitQuality, attackerID, icons, damageHtml) 442 443 @telemetry.ZONE_METHOD 444 def AddText(self, msgtext, msgtype = None, msg = None): 445 timestamp = blue.os.GetWallclockTime() 446 if not self.broken: 447 formattedTime = self.GetFormattedTime(timestamp) 448 formattedMessage = '[%20s ] (%s) %s\n' % (formattedTime, msgtype, msgtext) 449 encodedMessage = formattedMessage.encode('utf8', 'replace') 450 self.msglog.append(encodedMessage) 451 if not self.ShowMessage(msgtype): 452 return 453 msgData = (msgtext, msgtype, timestamp) 454 self.messages.append(msgData) 455 maxlog = log_message_count_setting.get() 456 if len(self.messages) > maxlog * 2: 457 self.messages = self.messages[-maxlog:] 458 wnd = self.GetWndWithFrameCounter() 459 if wnd and not wnd.destroyed: 460 self.addmsg.append(msgData) 461 462 @telemetry.ZONE_METHOD 463 def GetFormattedTime(self, timestamp, *args): 464 year, month, wd, day, hour, min, sec, ms = self.GetTimePartsForLog(timestamp) 465 return '%d.%.2d.%.2d %.2d:%.2d:%.2d' % (year, 466 month, 467 day, 468 hour, 469 min, 470 sec) 471 472 def SayMessage(self, text, msgType, icons = None, icon_color = None): 473 message = self._MakeOrGetMessageUI() 474 message.AddActionMessage(text, icons=icons, icon_color=icon_color) 475 self.AddText(text, msgType) 476 477 def Say(self, msgtext, hitQuality, attackerID, *args): 478 message = self._MakeOrGetMessageUI() 479 message.AddMessage(msgtext, hitQuality, attackerID, *args) 480 481 def CloseMessageUI(self): 482 message = getattr(uicore.layer.target, 'message', None) 483 if message and not message.destroyed: 484 message.Close() 485 486 def _MakeOrGetMessageUI(self): 487 message = getattr(uicore.layer.target, 'message', None) 488 if not message or message.destroyed: 489 message = CombatMessage(parent=uicore.layer.target, name='combatMessage', state=uiconst.UI_PICKCHILDREN) 490 uicore.layer.target.message = message 491 return message 492 493 def ShowMessage(self, msgtype): 494 setting = MESSAGE_FILTER_SETTING_BY_MESSAGE_TYPE.get(msgtype, None) 495 if setting is not None: 496 return setting.get() 497 else: 498 return True 499 500 def DumpToLog(self): 501 uthread.new(self.DumpToLogThread) 502 503 @telemetry.ZONE_METHOD 504 def DumpToLogThread(self): 505 if self.broken or not self.msglog: 506 return 507 logfile = None 508 try: 509 filename = self.GetLogfileName() 510 try: 511 logfile = file(filename, 'a') 512 except: 513 if self.newfileAttempts < 3: 514 LogWarn('Failed to open the logfile %s, creating new logfile...' % filename) 515 filename = self.GetLogfileName(reset=True) 516 LogWarn('new logfile name is: ', filename) 517 self.newfileAttempts += 1 518 logfile = file(filename, 'a') 519 else: 520 self.broken = 1 521 LogException(toAlertSvc=0) 522 sys.exc_clear() 523 524 if logfile: 525 try: 526 logfile.writelines(self.msglog) 527 logfile.close() 528 except IOError: 529 log.LogTraceback() 530 sys.exc_clear() 531 532 self.msglog = [] 533 except: 534 LogException(toAlertSvc=0) 535 sys.exc_clear() 536 537 if logfile and not logfile.closed: 538 logfile.close() 539 540 def CopyLog(self): 541 self.DumpToLog() 542 logfile = None 543 try: 544 filename = self.GetLogfileName() 545 logfile = file(filename) 546 ret = logfile.read() 547 logfile.close() 548 blue.pyos.SetClipboardData(ret) 549 except: 550 LogException() 551 sys.exc_clear() 552 553 if logfile and not logfile.closed: 554 logfile.close() 555 556 def GetLogfileName(self, reset = False): 557 if reset: 558 self.Reset() 559 year, month, weekday, day, hour, minute, second, msec = self.GetTimePartsForLog(self.resettime) 560 postFix = '_%s' % session.charid if session.charid else '' 561 filename = '%d%.2d%.2d_%.2d%.2d%.2d%s' % (year, 562 month, 563 day, 564 hour, 565 minute, 566 second, 567 postFix) 568 filename = blue.sysinfo.GetUserDocumentsDirectory() + '/EVE/logs/Gamelogs/%s.txt' % filename 569 return filename 570 571 def _PersistWorker(self): 572 while self.IsRunning(): 573 blue.pyos.synchro.SleepWallclock(self.persistInterval) 574 self.DumpToLog() 575 576 def MoveNotifications(self, enterMode, *args): 577 self.SetDragModeState(state=enterMode) 578 message = getattr(uicore.layer.target, 'message', None) 579 if not message or message.destroyed: 580 message = CombatMessage(parent=uicore.layer.target, name='combatMessage', state=uiconst.UI_PICKCHILDREN, width=400, height=60) 581 uicore.layer.target.message = message 582 message.SetSizeAndPosition() 583 msg = None 584 for each in uicore.layer.abovemain.children[:]: 585 if each.name == 'message': 586 msg = each 587 break 588 589 if msg is None: 590 from carbonui.control.message import QuickMessage 591 msg = QuickMessage(parent=uicore.layer.abovemain, name='message', height=40, width=300, state=uiconst.UI_HIDDEN) 592 if enterMode: 593 msg.EnterDragMode() 594 message.EnterDragMode() 595 else: 596 msg.ExitDragMode() 597 message.ExitDragMode() 598 599 def IsInDragMode(self, *args): 600 return self.inMovingMode 601 602 def SetDragModeState(self, state, *args): 603 self.inMovingMode = state 604 605 606 log_message_count_setting = UserSettingEnum(settings_key='logmessageamount', default_value=100, options=[100, 1000]) 607 show_info_messages_setting = UserSettingBool(settings_key='showinfologmessages', default_value=True) 608 show_warning_messages_setting = UserSettingBool(settings_key='showwarninglogmessages', default_value=True) 609 show_error_messages_setting = UserSettingBool(settings_key='showerrorlogmessages', default_value=True) 610 show_combat_messages_setting = UserSettingBool(settings_key='showcombatlogmessages', default_value=True) 611 show_notify_messages_setting = UserSettingBool(settings_key='shownotifylogmessages', default_value=True) 612 show_question_messages_setting = UserSettingBool(settings_key='showquestionlogmessages', default_value=True) 613 show_mining_messages_setting = UserSettingBool(settings_key='showmininglogmessages', default_value=True) 614 show_bounty_messages_setting = UserSettingBool(settings_key='showbountylogmessages', default_value=True) 615 MESSAGE_FILTER_SETTING_BY_MESSAGE_TYPE = {MessageType.INFO: show_info_messages_setting, 616 MessageType.WARNING: show_warning_messages_setting, 617 MessageType.ERROR: show_error_messages_setting, 618 MessageType.COMBAT: show_combat_messages_setting, 619 MessageType.NOTIFY: show_notify_messages_setting, 620 MessageType.QUESTION: show_question_messages_setting, 621 MessageType.MINING: show_mining_messages_setting, 622 MessageType.BOUNTY: show_bounty_messages_setting} 623 LABEL_BY_MESSAGE_TYPE = {MessageType.ERROR: 'UI/Accessories/Log/LogError', 624 MessageType.WARNING: 'UI/Accessories/Log/LogWarn', 625 MessageType.SLASH: 'UI/Accessories/Log/LogSlash', 626 MessageType.COMBAT: 'UI/Accessories/Log/LogCombat', 627 MessageType.NOTIFY: 'UI/Accessories/Log/LogNotify', 628 MessageType.QUESTION: 'UI/Accessories/Log/LogQuestion', 629 MessageType.INFO: 'UI/Accessories/Log/LogInfo', 630 MessageType.HINT: 'UI/Accessories/Log/LogHint', 631 MessageType.MINING: 'UI/Accessories/Log/LogMining', 632 MessageType.BOUNTY: 'UI/Accessories/Log/LogBounty'} 633 LABEL_BY_MESSAGE_FILTER_SETTING = {show_info_messages_setting: 'UI/Accessories/Log/ShowInfo', 634 show_warning_messages_setting: 'UI/Accessories/Log/ShowWarn', 635 show_error_messages_setting: 'UI/Accessories/Log/ShowError', 636 show_combat_messages_setting: 'UI/Accessories/Log/ShowCombat', 637 show_notify_messages_setting: 'UI/Accessories/Log/ShowNotify', 638 show_question_messages_setting: 'UI/Accessories/Log/ShowQuestion', 639 show_mining_messages_setting: 'UI/Accessories/Log/ShowMining', 640 show_bounty_messages_setting: 'UI/Accessories/Log/ShowBounty'} 641 642 class ShowDamageMessageSetting(UserSettingBool): 643 _on_available_changed = None 644 645 def __init__(self, settings_key, default_value, caption_label, hint_label = None, depends_on = None): 646 self._caption = caption_label 647 self._hint = hint_label 648 self._dependency = depends_on 649 super(ShowDamageMessageSetting, self).__init__(settings_key=settings_key, default_value=default_value) 650 if self._dependency: 651 self._dependency.on_change.connect(self._on_dependency_changed) 652 653 @property 654 def available(self): 655 if self._dependency is not None: 656 return self._dependency.get() 657 return True 658 659 @property 660 def on_available_changed(self): 661 if self._on_available_changed is None: 662 self._on_available_changed = signals.Signal('{}.on_available_changed'.format(self.__class__.__name__)) 663 return self._on_available_changed 664 665 @property 666 def caption(self): 667 return GetByLabel(self._caption) 668 669 @property 670 def hint(self): 671 return GetByLabel(self._hint) 672 673 def _on_dependency_changed(self, value): 674 if self._on_available_changed is not None: 675 self._on_available_changed(self) 676 677 678 show_inflight_notify_messages_setting = ShowDamageMessageSetting(settings_key='notifyMessagesEnabled', default_value=True, caption_label='UI/SystemMenu/GeneralSettings/Inflight/ShowTacticalNotifications', hint_label='UI/SystemMenu/GeneralSettings/Inflight/ShowTacticalNotificationTooltip') 679 show_inflight_damage_messages_setting = ShowDamageMessageSetting(settings_key='damageMessages', default_value=True, caption_label='UI/SystemMenu/GeneralSettings/Inflight/DamageNotifications') 680 show_inflight_energy_warfare_messages_setting = ShowDamageMessageSetting(settings_key='energyWarfareMessages', default_value=True, caption_label='UI/SystemMenu/GeneralSettings/Inflight/ShowEnergyWarfareNotifications') 681 show_inflight_remote_assistance_messages_setting = ShowDamageMessageSetting(settings_key='remoteAssistanceMessages', default_value=True, caption_label='UI/SystemMenu/GeneralSettings/Inflight/ShowRemoteAssistanceNotifications') 682 show_inflight_bounty_messages_setting = ShowDamageMessageSetting(settings_key='actionMessagesBounty', default_value=True, caption_label='UI/SystemMenu/GeneralSettings/Inflight/ShowBountyNotifications') 683 show_inflight_mining_messages_setting = ShowDamageMessageSetting(settings_key='actionMessagesMining', default_value=True, caption_label='UI/SystemMenu/GeneralSettings/Inflight/ShowMiningNotification') 684 show_inflight_damage_miss_messages_setting = ShowDamageMessageSetting(settings_key='damageMessagesNoDamage', default_value=True, caption_label='UI/SystemMenu/GeneralSettings/Inflight/MissedHitNotifications', depends_on=show_inflight_damage_messages_setting) 685 show_inflight_damage_incoming_messages_setting = ShowDamageMessageSetting(settings_key='damageMessagesMine', default_value=True, caption_label='UI/SystemMenu/GeneralSettings/Inflight/ShowInflictedDamageNotifications', depends_on=show_inflight_damage_messages_setting) 686 show_inflight_damage_outgoing_messages_setting = ShowDamageMessageSetting(settings_key='damageMessagesEnemy', default_value=True, caption_label='UI/SystemMenu/GeneralSettings/Inflight/ShowIncurredDamageNotification', depends_on=show_inflight_damage_messages_setting) 687 show_inflight_remote_assistance_incoming_messages_setting = ShowDamageMessageSetting(settings_key='remoteAssistanceMessagesMine', default_value=True, caption_label='UI/SystemMenu/GeneralSettings/Inflight/ShowIncomingRemoteAssistanceNotifications', depends_on=show_inflight_remote_assistance_messages_setting) 688 show_inflight_remote_assistance_outgoing_messages_setting = ShowDamageMessageSetting(settings_key='remoteAssistanceMessagesOthers', default_value=True, caption_label='UI/SystemMenu/GeneralSettings/Inflight/ShowOutgoingRemoteAssistanceNotifications', depends_on=show_inflight_remote_assistance_messages_setting) 689 SHOW_INFLIGHT_MESSAGE_SETTINGS = (show_inflight_notify_messages_setting, 690 show_inflight_damage_messages_setting, 691 show_inflight_energy_warfare_messages_setting, 692 show_inflight_remote_assistance_messages_setting, 693 show_inflight_bounty_messages_setting, 694 show_inflight_mining_messages_setting, 695 show_inflight_damage_miss_messages_setting, 696 show_inflight_damage_incoming_messages_setting, 697 show_inflight_damage_outgoing_messages_setting, 698 show_inflight_remote_assistance_incoming_messages_setting, 699 show_inflight_remote_assistance_outgoing_messages_setting) 700 701 class ShowInflightMessageCheckboxMenuEntryData(MenuEntryDataCheckbox): 702 703 def __init__(self, setting): 704 super(ShowInflightMessageCheckboxMenuEntryData, self).__init__(text=setting.caption, setting=setting, isEnabled=setting.available) 705 setting.on_available_changed.connect(self._on_available_changed) 706 707 def _on_available_changed(self, setting): 708 self.isEnabled = setting.available 709 710 711 class DependentCheckboxMenuEntryData(MenuEntryDataCheckbox): 712 713 def __init__(self, text, setting, dependencies): 714 self._dependencies = dependencies 715 super(DependentCheckboxMenuEntryData, self).__init__(text=text, setting=setting, isEnabled=self._are_all_dependencies_enabled()) 716 for dependency in self._dependencies: 717 dependency.on_change.connect(self._on_dependency_changed) 718 719 def _on_dependency_changed(self, value): 720 self.isEnabled = self._are_all_dependencies_enabled() 721 722 def _are_all_dependencies_enabled(self): 723 return all((dependency.get() for dependency in self._dependencies)) 724 725 726 show_inflight_messages_setting = UserSettingBool(settings_key='damageMessageShowInspaceNotifications', default_value=True) 727 show_inflight_message_ship_setting = UserSettingBool(settings_key='damageMessagesShowShip', default_value=False) 728 show_inflight_message_ticker_setting = UserSettingBool(settings_key='damageMessagesShowTicker', default_value=False) 729 show_inflight_message_weapon_setting = UserSettingBool(settings_key='damageMessagesShowWeapon', default_value=False) 730 show_inflight_message_quality_setting = UserSettingBool(settings_key='damageMessagesShowQuality', default_value=True) 731 inflight_message_font_size_setting = UserSettingEnum(settings_key='dmgnotifictions_fontsize', options=[10, 12, 14], default_value=12) 732 inflight_messages_alignment_setting = UserSettingEnum(settings_key='dmgnotifictions_alignment', options=['auto', 733 'left', 734 'right', 735 'center'], default_value='auto') 736 CAPTION_LABEL_BY_INFLIGHT_MESSAGE_ALIGNMENT = {'auto': 'UI/Accessories/Log/AlignmentAuto', 737 'left': 'UI/Accessories/Log/AlignmentLeft', 738 'right': 'UI/Accessories/Log/AlignmentRight', 739 'center': 'UI/Accessories/Log/AlignmentCenter'} 740 741 class LoggerWindow(Window): 742 default_isLightBackground = True 743 default_windowID = 'logger' 744 default_captionLabelPath = 'UI/Accessories/Log/Log' 745 default_iconNum = 'res:/ui/Texture/WindowIcons/log.png' 746 default_minSize = (256, 136) 747 default_width = 400 748 default_height = 200 749 default_isCompactable = True 750 _actions = None 751 _toolbar = None 752 753 def __init__(self, **kwargs): 754 super(LoggerWindow, self).__init__(**kwargs) 755 self._toolbar = ContainerAutoSize(parent=self.content, align=uiconst.TOTOP, padLeft=-4) 756 self._actions = ContainerAutoSize(parent=self._get_actions_parent(), align=self._get_actions_alignment(), height=24, alignMode=uiconst.TOLEFT) 757 self.scroll = eveScroll.Scroll(parent=self.content, align=uiconst.TOALL, padLeft=-8) 758 self.scroll.Load(contentList=[], headers=[GetByLabel('UI/Common/DateWords/Time'), GetByLabel('UI/Accessories/Log/Type'), GetByLabel('UI/Accessories/Log/Message')]) 759 self.scroll.sr.id = 'logScroll' 760 MenuButtonIcon(parent=ContainerAutoSize(parent=self._actions, align=uiconst.TOLEFT), align=uiconst.TOPLEFT, width=24, height=24, iconSize=16, texturePath=eveicon.filter, get_menu_func=self._get_log_filter_menu, hint=GetByLabel('UI/Generic/Filters')) 761 MenuButtonIcon(parent=ContainerAutoSize(parent=self._actions, align=uiconst.TOLEFT), align=uiconst.TOPLEFT, width=24, height=24, iconSize=16, texturePath=eveicon.settings, get_menu_func=self._get_combat_message_menu, hint=GetByLabel('UI/Accessories/Log/CombatSettings')) 762 self.LoadAllMessages() 763 self.timer = timerstuff.AutoTimer(1000, self.CheckMessages) 764 log_message_count_setting.on_change.connect(self._on_log_message_count_setting_changed) 765 show_inflight_messages_setting.on_change.connect(self._on_show_inflight_messages_setting_changed) 766 inflight_message_font_size_setting.on_change.connect(self._on_inflight_message_font_size_setting_changed) 767 inflight_messages_alignment_setting.on_change.connect(self._on_inflight_messages_alignment_setting_changed) 768 for setting in MESSAGE_FILTER_SETTING_BY_MESSAGE_TYPE.values(): 769 setting.on_change.connect(self._on_message_filter_setting_changed) 770 771 self.on_compact_mode_changed.connect(self._on_window_compact_mode_changed) 772 self.on_stacked_changed.connect(self._on_window_stacked_changed) 773 774 def _get_actions_parent(self): 775 if self.compact and not self.stacked: 776 return self.header.extra_content 777 else: 778 return self._toolbar 779 780 def _get_actions_alignment(self): 781 if self.compact and not self.stacked: 782 return uiconst.CENTERRIGHT 783 else: 784 return uiconst.TOPLEFT 785 786 def _update_actions(self): 787 self._actions.SetParent(None) 788 self._actions.align = self._get_actions_alignment() 789 self._actions.SetParent(self._get_actions_parent()) 790 791 def _on_window_compact_mode_changed(self, window): 792 self._update_actions() 793 794 def _on_window_stacked_changed(self, window): 795 self._update_actions() 796 797 def _PrepareListEntries(self, messages): 798 showmsgs = [] 799 dateSortKey = 'sort_%s' % GetByLabel('UI/Common/DateWords/Time') 800 openMsgTitle = GetByLabel('UI/Accessories/Log/LogMessage') 801 for msg in messages: 802 msgtext, msgtype, timestamp = msg 803 if not self.ShowMessage(msgtype): 804 continue 805 color = COLORS_BY_TYPE.get(msgtype, eveColor.WHITE_HEX) 806 color = u'<color={}>'.format(color) 807 if msgtype: 808 if msgtype in LABEL_BY_MESSAGE_TYPE: 809 label = GetByLabel(LABEL_BY_MESSAGE_TYPE[msgtype]) 810 else: 811 label = msgtype 812 else: 813 label = GetByLabel('UI/Accessories/Log/Generic') 814 text = GetByLabel('UI/Accessories/Log/MessageOutput', logtime=timestamp, color=color, label=label, message=StripTags(msgtext, stripOnly=['t'])) 815 entry = GetFromClass(Generic, {'label': text, 816 'canOpen': openMsgTitle, 817 dateSortKey: timestamp, 818 'line': 1}) 819 showmsgs.append(entry) 820 821 return showmsgs 822 823 @telemetry.ZONE_METHOD 824 def LoadAllMessages(self): 825 maxlog = log_message_count_setting.get() 826 messages = sm.GetService('logger').GetMessages() 827 if len(messages) > maxlog: 828 messages = messages[-maxlog:] 829 showmsgs = self._PrepareListEntries(messages) 830 self.scroll.Load(contentList=showmsgs, headers=[GetByLabel('UI/Common/DateWords/Time'), GetByLabel('UI/Accessories/Log/Type'), GetByLabel('UI/Accessories/Log/Message')]) 831 832 @telemetry.ZONE_METHOD 833 def CheckMessages(self): 834 if self.scroll.IsVisible(): 835 messages = sm.GetService('logger').GetPendingMessages() 836 if messages: 837 entryList = self._PrepareListEntries(messages) 838 maxlog = log_message_count_setting.get() 839 self.scroll.AddEntries(0, entryList) 840 revSorting = self.scroll.GetSortDirection() or self.scroll.GetSortBy() is None 841 if revSorting: 842 self.scroll.RemoveEntries(self.scroll.GetNodes()[maxlog:]) 843 else: 844 self.scroll.RemoveEntries(self.scroll.GetNodes()[:-maxlog]) 845 846 def GetMenu(self, *args): 847 m = super(LoggerWindow, self).GetMenu(*args) 848 return m 849 850 def GetMenuMoreOptions(self): 851 menu = super(LoggerWindow, self).GetMenuMoreOptions() 852 menu.AddEntry(text=MenuLabel('UI/Accessories/Log/CaptureLog'), func=sm.GetService('logger').DumpToLog) 853 menu.AddEntry(text=MenuLabel('UI/Accessories/Log/CopyLog'), func=sm.GetService('logger').CopyLog, texturePath=eveicon.copy) 854 menu.AddSeparator() 855 menu.AddEntry(GetByLabel('UI/Accessories/Log/ClearLog'), self.ClearAllMessages, texturePath=eveicon.trashcan) 856 return menu 857 858 def _get_log_filter_menu(self): 859 menu = MenuData() 860 menu.AddCaption(GetByLabel('UI/Accessories/Log/MessageTypesToLog')) 861 for setting, label in LABEL_BY_MESSAGE_FILTER_SETTING.items(): 862 menu.AddCheckbox(text=GetByLabel(label), setting=setting) 863 864 menu.AddCaption(GetByLabel('UI/Accessories/Log/NumMessagesInLogView')) 865 for message_count in log_message_count_setting.options: 866 menu.AddRadioButton(text=unicode(message_count), setting=log_message_count_setting, value=message_count) 867 868 return menu 869 870 def ResetDmgNotificationAlignment(self, *args): 871 settings.char.ui.Delete('damageMessages_config') 872 inflight_messages_alignment_setting.reset() 873 message = getattr(uicore.layer.target, 'message', None) 874 if message: 875 message.ResetAlignment() 876 message = itertoolsext.first_or_default(uicore.layer.abovemain.children, predicate=lambda each: each.name == 'message') 877 if message: 878 message.ResetAlignment() 879 880 def _get_combat_message_menu(self): 881 menu = MenuData() 882 menu.AddEntry(text=GetByLabel('UI/Accessories/Log/CombatMessagesToDisplay'), subMenuData=[ ShowInflightMessageCheckboxMenuEntryData(setting) for setting in SHOW_INFLIGHT_MESSAGE_SETTINGS ]) 883 inflight_message_sub_menu = MenuData() 884 inflight_message_sub_menu.AppendMenuEntryData(DependentCheckboxMenuEntryData(text=GetByLabel('UI/Accessories/Log/ShowInspaceNotifications'), setting=show_inflight_messages_setting, dependencies=[show_inflight_damage_messages_setting])) 885 inflight_message_sub_menu.AddSeparator() 886 inflight_message_sub_menu.AppendMenuEntryData(DependentCheckboxMenuEntryData(text=GetByLabel('UI/Accessories/Log/DmgNotificationsShowShip'), setting=show_inflight_message_ship_setting, dependencies=[show_inflight_messages_setting, show_inflight_damage_messages_setting])) 887 inflight_message_sub_menu.AppendMenuEntryData(DependentCheckboxMenuEntryData(text=GetByLabel('UI/Accessories/Log/DmgNotificationsShowTicker'), setting=show_inflight_message_ticker_setting, dependencies=[show_inflight_messages_setting, show_inflight_damage_messages_setting])) 888 inflight_message_sub_menu.AppendMenuEntryData(DependentCheckboxMenuEntryData(text=GetByLabel('UI/Accessories/Log/DmgNotificationsShowWeapon'), setting=show_inflight_message_weapon_setting, dependencies=[show_inflight_messages_setting, show_inflight_damage_messages_setting])) 889 inflight_message_sub_menu.AppendMenuEntryData(DependentCheckboxMenuEntryData(text=GetByLabel('UI/Accessories/Log/DmgNotificationsShowQuality'), setting=show_inflight_message_quality_setting, dependencies=[show_inflight_messages_setting, show_inflight_damage_messages_setting])) 890 inflight_message_sub_menu.AddSeparator() 891 drag_mode_active = sm.GetService('logger').IsInDragMode() 892 inflight_message_sub_menu.AddEntry(text=GetByLabel('UI/Accessories/Log/ExitMessageMovingMode') if drag_mode_active else GetByLabel('UI/Accessories/Log/EnterMessageMovingMode'), func=lambda : sm.GetService('logger').MoveNotifications(not drag_mode_active), texturePath=eveicon.caret_up_down_left_right) 893 menu.AddEntry(text=GetByLabel('UI/Accessories/Log/ExtraInfoHeader'), subMenuData=inflight_message_sub_menu) 894 menu.AddEntry(text=GetByLabel('UI/Accessories/Log/FontSize'), subMenuData=[ MenuEntryDataRadioButton(text=unicode(font_size), setting=inflight_message_font_size_setting, value=font_size) for font_size in inflight_message_font_size_setting.options ]) 895 alignment_sub_menu = MenuData(entryList=[ MenuEntryDataRadioButton(text=GetByLabel(CAPTION_LABEL_BY_INFLIGHT_MESSAGE_ALIGNMENT.get(alignment)), setting=inflight_messages_alignment_setting, value=alignment) for alignment in inflight_messages_alignment_setting.options ]) 896 alignment_sub_menu.AddSeparator() 897 alignment_sub_menu.AddEntry(text=GetByLabel('UI/Accessories/Log/ResetAlignment'), func=self.ResetDmgNotificationAlignment, menuGroupID=DESTRUCTIVEGROUP) 898 menu.AddEntry(text=GetByLabel('UI/Accessories/Log/InspaceNotificationFontAlignment'), subMenuData=alignment_sub_menu) 899 return menu 900 901 def ClearAllMessages(self): 902 sm.GetService('logger').ClearAllMessages() 903 self.LoadAllMessages() 904 905 def OnEndMaximize(self, *args): 906 self.LoadAllMessages() 907 908 def _OnClose(self, *args): 909 self.timer = None 910 911 def ShowMessage(self, msgtype): 912 return sm.GetService('logger').ShowMessage(msgtype) 913 914 def _on_log_message_count_setting_changed(self, value): 915 self.LoadAllMessages() 916 917 def _on_message_filter_setting_changed(self, value): 918 self.LoadAllMessages() 919 920 @staticmethod 921 def _on_show_inflight_messages_setting_changed(value): 922 message_box = getattr(uicore.layer.target, 'message', None) 923 if message_box and not message_box.destroyed: 924 message_box.Close() 925 926 @staticmethod 927 def _on_inflight_message_font_size_setting_changed(value): 928 sm.GetService('logger').notificationFontSize = value 929 message = getattr(uicore.layer.target, 'message', None) 930 if message: 931 message.ChangeFontSize() 932 933 @staticmethod 934 def _on_inflight_messages_alignment_setting_changed(value): 935 message = getattr(uicore.layer.target, 'message', None) 936 if message: 937 message.ChangeAlignment()