bugReporting.py (53876B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\services\bugReporting.py 2 import carbonversion 3 import json 4 import os 5 import platform 6 import subprocess 7 import utillib 8 import yaml 9 import zipfile 10 import time 11 from carbon.common.script.sys import basesession 12 from carbon.common.script.sys.service import Service 13 from carbon.common.script.sys.serviceConst import ROLE_GML 14 from carbon.common.script.util import timerstuff 15 from carbon.common.script.util.format import FmtDate 16 from carbonui.control.combo import Combo 17 from carbonui.control.singlelineedits.singleLineEditText import SingleLineEditText 18 from carbonui.primitives.containerAutoSize import ContainerAutoSize 19 from carbonui.primitives.frame import Frame 20 from carbonui.primitives.layoutGrid import LayoutGrid 21 from carbonui.util.various_unsorted import GetWindowAbove 22 from eve.common.script.sys import eveCfg 23 from eveexceptions import UserError 24 from globalConfig.getFunctions import GetBugReportServer 25 import blue 26 import carbonversion 27 import uthread 28 import log 29 import trinity 30 import gatekeeper 31 import webbrowser 32 from localization import GetByLabel, GetByMessageID 33 from eveexceptions.exceptionEater import ExceptionEater 34 from carbon.common.lib.serverInfo import GetServerInfo 35 from omega.client.clone_grade_svc import get_clone_grade_svc 36 from player_messaging.client.ui_message import prompt_player_fsd_dialog 37 from shipfitting.exportFittingUtil import GetFittingEFTString 38 from carbonui import uiconst 39 from carbonui.uicore import uicore 40 from carbonui.primitives.container import Container 41 from carbonui.primitives.fill import Fill 42 from carbonui.primitives.sprite import Sprite 43 from carbonui.primitives.desktop import UIRoot 44 from carbonui.button.group import ButtonGroup 45 from carbonui.control.button import Button 46 from eve.client.script.ui.control.eveEditPlainText import EditPlainText 47 from eve.client.script.ui.control.eveLabel import EveLabelMedium, EveLabelSmall, Label 48 from eve.client.script.ui.control.eveLoadingWheel import LoadingWheel 49 from carbonui.control.window import Window 50 from eve.client.script.ui.control.infoIcon import MoreInfoIcon 51 from eve.common.lib import appConst as const 52 from bugreporter import BugReporter, SendAttachmentError 53 from eveprefs import prefs, boot 54 LEFT_ARROW = 'res:/UI/Texture/BugReport_Arrow_Left.png' 55 RIGHT_ARROW = 'res:/UI/Texture/BugReport_Arrow_Right.png' 56 COLOR_GREEN = (0.0, 57 1.0, 58 0.0, 59 1.0) 60 COLOR_RED = (1.0, 61 0.0, 62 0.0, 63 1.0) 64 COLOR_BLUE = (0.0, 65 0.0, 66 1.0, 67 1.0) 68 ARROW_HEIGHT = 28 69 BASEFONTSIZE = 18 70 NUMFONTSIZE = 26 71 BUGREPORT_SERVER = 'https://ebr-service.ccpeveweb.com/v1' 72 BUG_REPORTS_URL = 'https://community.eveonline.com/support/bug-reports/my-bug-reports/' 73 BUG_GUIDE_URL = 'https://community.eveonline.com/support/test-servers/bug-reporting/' 74 SCREENSHOT_NAME = 'igbr_%s.jpg' 75 NUM_SCREENSHOTS = 3 76 SAVE_NAME = 'igbr_save_%s.yaml' 77 78 def HIWORD(f): 79 return (f & 4294901760L) >> 16 80 81 82 def LOWORD(f): 83 return f & 65535 84 85 86 def HIPART(f): 87 return (f & 18446744069414584320L) >> 32 88 89 90 def LOPART(f): 91 return f & 4294967295L 92 93 94 def DecodeDriverVersion(version): 95 try: 96 highpart = HIPART(version) 97 lowpart = LOPART(version) 98 product = HIWORD(highpart) 99 version = LOWORD(highpart) 100 subVersion = HIWORD(lowpart) 101 build = LOWORD(lowpart) 102 return '%s.%s.%s.%s' % (product, 103 version, 104 subVersion, 105 build) 106 except: 107 return '' 108 109 110 def AbsPath(path): 111 return blue.paths.ResolvePathForWriting(path) 112 113 114 class BugReportingService(Service): 115 __guid__ = 'svc.bugReporting' 116 __startupdependencies__ = ['machoNet'] 117 118 def __init__(self): 119 super(BugReportingService, self).__init__() 120 self.dxDiagFileName = None 121 self.categories = None 122 self.startStages = None 123 self.fixInStages = None 124 self.assignees = None 125 self.priorities = None 126 self.severities = None 127 self.serverUrl = '' 128 self.windowsUserName = '' 129 self.charName = '' 130 self.userName = '' 131 self.bugReportServer = None 132 self.screenshots = [] 133 self.wnd = None 134 self.categories = None 135 136 def StartCreateBugReport(self): 137 self.OpenWindow() 138 139 def OpenWindow(self): 140 self.Init() 141 self.wnd = BugReportingWindow.Open() 142 143 def Init(self): 144 self.screenshots = [] 145 self.bugReportServer = GetBugReportServer(self.machoNet, BUGREPORT_SERVER) 146 self.bugReporter = BugReporter(self.bugReportServer, os.path.join(blue.paths.ResolvePath('cache:'), 'IGBR')) 147 self.LogNotice('Opening bug reporting', self.userName, self.charName) 148 if session.charid: 149 self.charName = cfg.eveowners.Get(session.charid).name 150 self.userName = settings.public.ui.Get('username', '') 151 try: 152 self.windowsUserName = os.environ.get('USERNAME') 153 except: 154 self.LogError('Unable to get windows username from environment') 155 156 self.collectedData = None 157 self.collectedFiles = None 158 self.outputFolder = '' 159 self.settingsFolder = 'settings:/../../' 160 self.outputFolder = self.settingsFolder + u'IGBR/' 161 try: 162 os.mkdir(AbsPath(self.outputFolder)) 163 except: 164 pass 165 166 if self.dxDiagFileName is None: 167 self.dxDiagFileName = AbsPath(self.outputFolder + 'dxdiag.txt') 168 try: 169 os.remove(self.dxDiagFileName) 170 except OSError: 171 pass 172 173 try: 174 self.LogInfo('Getting DXDiag info...') 175 subprocess.Popen(['dxdiag.exe', '/t', self.dxDiagFileName]) 176 except: 177 self.dxDiagFileName = None 178 179 def SaveToDisk(self, data): 180 data['screenshots'] = self.screenshots 181 f = open(AbsPath(self.outputFolder + self.GetSaveName()), 'w') 182 yaml.safe_dump(data, f) 183 f.close() 184 185 def GetSaveName(self): 186 name = SAVE_NAME 187 name = name % 'br' 188 return name 189 190 def LoadFromDisk(self): 191 path = AbsPath(self.outputFolder + self.GetSaveName()) 192 self.LogInfo('Loading data from disk', path) 193 try: 194 f = open(path, 'r') 195 except: 196 raise UserError('CustomInfo', {'info': 'No saved data found.'}) 197 198 data = yaml.safe_load(f) 199 f.close() 200 self.screenshots = data.get('screenshots', []) 201 return data 202 203 def CollectData(self): 204 serverInfo = GetServerInfo() 205 serverVersion, serverBuild = sm.RemoteSvc('cache').GetServerVersionAndBuild() 206 collectedData = {'title': None, 207 'description': None, 208 'reproSteps': None, 209 'session': str(session), 210 'clientDateTime': FmtDate(blue.os.GetWallclockTime()), 211 'clientVersion': boot.keyval['version'].split('=', 1)[1], 212 'clientBuild': boot.build, 213 'clientBranch': boot.branch, 214 'serverName': serverInfo.name, 215 'serverVersion': serverVersion, 216 'serverBuild': serverBuild, 217 'serverEspUrl': serverInfo.espUrl, 218 'memoryFree': blue.sysinfo.GetMemory().availablePhysical / 1024} 219 bitCount = blue.sysinfo.cpu.bitCount 220 adapters = trinity.adapters 221 ident = adapters.GetAdapterInfo(trinity.mainWindow.GetWindowState().adapter) 222 driverVersion = DecodeDriverVersion(ident.driverVersion) 223 computerInfo = {'memoryPhysical': blue.sysinfo.GetMemory().totalPhysical / 1024, 224 'cpuArchitecture': blue.sysinfo.cpu.architecture, 225 'cpuIdentifier': blue.sysinfo.cpu.identifier, 226 'cpuLevel': blue.sysinfo.cpu.family, 227 'cpuRevision': blue.sysinfo.cpu.revision, 228 'cpuCount': blue.sysinfo.cpu.logicalCpuCount, 229 'cpuMHz': blue.sysinfo.cpu.frequency, 230 'cpuBitCount': bitCount, 231 'osMajorVersion': blue.sysinfo.os.majorVersion, 232 'osMinorVersion': blue.sysinfo.os.minorVersion, 233 'osBuild': blue.sysinfo.os.buildNumber, 234 'osPatch': blue.sysinfo.os.patch, 235 'osPlatform': 2, 236 'videoCardAdapter': ident.description.encode('ascii', 'ignore'), 237 'videoCardIdentifier': ident.deviceIdentifier[1:-1], 238 'videoDriverVersion': driverVersion, 239 'clientBitCount': blue.sysinfo.processBitCount} 240 try: 241 driverInfo = ident.GetDriverInfo() 242 computerInfo['videoDriverVersion'] = driverInfo.driverVersionString 243 computerInfo['videoDriverDate'] = driverInfo.driverDate 244 computerInfo['videoDriverVendor'] = driverInfo.driverVendor 245 computerInfo['videoIsOptimus'] = 'Yes' if driverInfo.isOptimus else 'No' 246 computerInfo['videoIsAmdDynamicSwitchable'] = 'Yes' if driverInfo.isAmdDynamicSwitchable else 'No' 247 except RuntimeError: 248 pass 249 250 collectedData.update({'computerInfo': computerInfo}) 251 collectedFiles = self.GetAttachments() 252 self.collectedData = utillib.KeyVal(data=collectedData, files=collectedFiles) 253 return self.collectedData 254 255 def GetScreenshotPath(self, n): 256 if n is not None and n < len(self.screenshots): 257 fileName = self.screenshots[n] 258 else: 259 fileName = self.outputFolder + SCREENSHOT_NAME % blue.os.GetTime() 260 return fileName 261 262 def GetScreenshot(self, n = None): 263 if n > NUM_SCREENSHOTS - 1 or n is None and len(self.screenshots) >= NUM_SCREENSHOTS: 264 raise UserError('CustomInfo', {'info': 'You have reached the maximum number of screenshots. Please delete a screenshot before grabbing abother one.'}) 265 fileName = self.GetScreenshotPath(n) 266 self.LogInfo('Getting screenshot number', n, 'to', fileName, '...') 267 self.wnd.HideAll() 268 blue.pyos.synchro.SleepWallclock(300) 269 trinity.SaveRenderTarget(AbsPath(fileName)) 270 if n is not None and n < len(self.screenshots): 271 self.screenshots[n] = fileName 272 else: 273 n = len(self.screenshots) 274 self.screenshots.append(fileName) 275 self.LogInfo('Screenshot has been captured. I now have', len(self.screenshots), 'screenshots') 276 self.wnd.Show() 277 self.wnd.DoEditScreenshot(n) 278 self.wnd.UpdateScreenshotButtons() 279 280 def DeleteScreenshot(self, n): 281 fileName = self.screenshots[n] 282 del self.screenshots[n] 283 try: 284 os.remove(fileName) 285 except OSError: 286 pass 287 288 self.wnd.UpdateScreenshotButtons() 289 290 def GetAttachments(self): 291 self.LogInfo('Getting Attachments...') 292 files = [] 293 screenShots = [] 294 ret = [] 295 try: 296 data = sm.GetService('monitor').GetInMemoryLogs() 297 logs = ('logs.txt', data) 298 files.append(logs) 299 if len(data) > 250: 300 ret += [logs] 301 except: 302 log.LogException('Error getting logs for bug reporting. Skipping.') 303 304 for i, fileName in enumerate(self.screenshots): 305 try: 306 with open(AbsPath(fileName), 'rb') as ssfile: 307 data = ssfile.read() 308 except IOError: 309 data = '' 310 311 f = (SCREENSHOT_NAME % i, data) 312 screenShots.append(f) 313 314 self._AppendProcessHealth(files) 315 self._AppendMethodCalls(files) 316 self._AppendOutstandingCalls(files) 317 self._AppendShipFitting(files) 318 self._AppendLatestCrashes(files) 319 self._AppendPrefsIni(files) 320 self._AppendSettings(files) 321 self._AppendDxdiag(files) 322 self._AppendPDMData(files) 323 self._AppendCarbonInfo(files) 324 ZIP_FILENAME = 'igbr.zip' 325 zipName = AbsPath(self.outputFolder + ZIP_FILENAME) 326 with zipfile.ZipFile(zipName, 'w', zipfile.ZIP_DEFLATED) as zipDataFile: 327 for fileName, data in files: 328 if not fileName.lower().endswith('.jpg'): 329 zipDataFile.writestr(fileName, data) 330 331 zipData = open(zipName, 'rb').read() 332 self.LogInfo('Done Getting Attachments!') 333 ret += screenShots 334 ret += [(ZIP_FILENAME, zipData)] 335 return ret 336 337 def _AppendProcessHealth(self, files): 338 try: 339 processHealthSvc = sm.GetService('processHealth') 340 data = processHealthSvc.GetAllLogs() 341 files.append(('processHealth.txt', data)) 342 except: 343 log.LogException('Error getting processhealth data for bug reporting. Skipping.') 344 345 def _AppendMethodCalls(self, files): 346 try: 347 data = sm.GetService('monitor').GetMethodCalls() 348 files.append(('methodcalls.txt', data)) 349 except: 350 log.LogException('Error getting method call timers for bug reporting. Skipping.') 351 352 def _AppendOutstandingCalls(self, files): 353 try: 354 data = '\n'.join(('%s - %s' % (FmtDate(t), n) for n, t in basesession.outstandingCallTimers)) 355 files.append(('outstandingcalls.txt', data)) 356 except Exception: 357 log.LogException('Failed to get outstanding calls') 358 359 def _AppendShipFitting(self, files): 360 try: 361 fittingSvc = sm.GetService('fittingSvc') 362 fitting = utillib.KeyVal() 363 fitting.shipTypeID, fitting.fitData, _ = fittingSvc.GetFittingDictForActiveShip() 364 fitting.name = cfg.evelocations.Get(eveCfg.GetActiveShip()).locationName 365 data = GetFittingEFTString(fitting).encode('utf-8') 366 files.append(('fitting.txt', data)) 367 except Exception: 368 log.LogException('Failed to get ship fitting') 369 370 def _AppendLatestCrashes(self, files): 371 try: 372 if platform.system() == 'Darwin': 373 crash_folder = os.path.join(blue.sysinfo.GetUserApplicationDataDirectory(), 'CCP', 'EVE', 'completed') 374 else: 375 crash_folder = os.path.join(blue.sysinfo.GetUserApplicationDataDirectory(), 'CCP', 'EVE', 'reports') 376 crashes = [ (os.path.splitext(i)[0], os.path.getmtime(os.path.join(crash_folder, i))) for i in os.listdir(crash_folder) if i.lower().endswith('.dmp') ] 377 except Exception: 378 return 379 380 crashes.sort(key=lambda x: x[1], reverse=True) 381 data = '\n'.join(('%s - %s' % (time.strftime('%Y-%m-%d %H:%M', time.gmtime(date)), crash) for crash, date in crashes[:10])) 382 files.append(('lastcrashes.txt', data)) 383 384 def _AppendPrefsIni(self, files): 385 try: 386 f = open(prefs.ini.filename, 'rb') 387 data = f.read() 388 f.close() 389 files.append((os.path.basename(prefs.ini.filename), data)) 390 except: 391 log.LogException('Error getting prefs for bug reporting. Skipping.') 392 393 def _AppendSettings(self, files): 394 allSettings = {} 395 try: 396 for settingsType in ('public', 'user', 'char'): 397 allSettings[settingsType] = getattr(settings, settingsType).datastore 398 399 data = yaml.dump(allSettings) 400 files.append(('settings.yaml', data)) 401 except: 402 log.LogException('Error getting settings for bug reporting. Skipping.') 403 404 def _AppendDxdiag(self, files): 405 try: 406 if self.dxDiagFileName: 407 i = 0 408 data = '' 409 while i < 30: 410 try: 411 f = open(self.dxDiagFileName, 'r') 412 data = f.read() 413 f.close() 414 if len(data) == 0: 415 raise IOError 416 break 417 except IOError: 418 i += 1 419 blue.pyos.synchro.SleepWallclock(1000) 420 421 else: 422 self.LogError('Failed to get DXDiag file after 30 seconds', self.dxDiagFileName) 423 self.dxDiagFileName = None 424 425 files.append(('dxdiag.txt', data)) 426 except: 427 log.LogException('Error getting DxDiag for bug reporting. Skipping.') 428 429 def _AppendPDMData(self, files): 430 try: 431 files.append(('PDMData.txt', blue.sysinfo.GetPDMData().encode('utf-8'))) 432 except: 433 log.LogException('Error getting PDM data for bug reporting. Skipping.') 434 435 def _AppendCarbonInfo(self, files): 436 try: 437 data = json.dumps(carbonversion.get_carbon_version().get_json_data()) 438 files.append(('carbon.json', data)) 439 except: 440 log.LogException('Error getting carbon info for bug reporting. Skipping.') 441 442 def GetLabelsForIssue(self, categoryID): 443 ret = [] 444 categoryName = self.GetCategoryName(categoryID).lower() 445 language = {'DE': 'German', 446 'EN': None, 447 'ES': 'Spanish', 448 'FR': 'French', 449 'JA': 'Japanese', 450 'KO': 'Korean', 451 'RU': 'Russian', 452 'ZH': 'Chinese'}.get(session.languageID, None) 453 if language is not None: 454 ret.append(language) 455 if 'graphic' in categoryName: 456 ret.append(trinity.platform) 457 if blue.sysinfo.isWine: 458 if blue.sysinfo.wineHostOs.startswith('Darwin'): 459 platformLabel = 'PlatformMacWine' 460 else: 461 platformLabel = 'PlatformLinuxWine' 462 ret.append(blue.sysinfo.wineVersion) 463 elif platform.system() == 'Darwin': 464 platformLabel = 'PlatformMac' 465 elif platform.system() == 'Windows': 466 platformLabel = 'PlatformWin' 467 if blue.sysinfo.os.majorVersion == 10 and blue.sysinfo.os.buildNumber >= 22000: 468 ret.append('Windows11') 469 else: 470 platformLabel = 'PlatformUnknown' 471 ret.append(platformLabel) 472 if blue.sysinfo.computerName == 'GEFORCE-NOW': 473 ret.append('GeforceNow') 474 if blue.sysinfo.isRosetta: 475 ret.append('Rosetta') 476 ret.append('IsOmega' if get_clone_grade_svc().is_omega_with_grace_period() else 'IsAlpha') 477 return ret 478 479 def SendDefectOrBugReport(self, data): 480 ret = self.CollectData() 481 computer = self._GetComputerInfo(ret, ret.data['computerInfo']) 482 sessionInfo = self._GetSessionInfo(self._GetEspUrl(ret)) 483 data['ServerName'] = ret.data['serverName'] 484 try: 485 data['Title'] = data['Title'].encode('UTF-8') 486 data['Description'] = data['Description'].encode('UTF-8') 487 data['ReproductionSteps'] = data['ReproductionSteps'].encode('UTF-8') 488 except Exception: 489 self.LogError('For some reason I do not particularly care about failing to encode title, description or reproductionSteps') 490 491 category = data['Category'] 492 labels = self.GetLabelsForIssue(category) 493 return self.bugReporter.SendBugReport(category, data['Title'], data['Description'], data['ReproductionSteps'], sessionInfo, computer, session.userid, labels, ret.data['serverName'], GetServerVersion(ret.data['serverBuild'], ret.data['serverVersion']), ret.files) 494 495 def _GetComputerInfo(self, ret, computerInfo): 496 computer = 'Trinity platform: %(triPlatform)s\r\nProcess: %(bits)s\r\nOS: %(os)s.%(osminor)s, build: %(build)s, %(servicepack)s\r\nVideo Card: %(videocard)s (Driver: %(driverversion)s, Released: %(driverdate)s)\r\nIs Optimus: %(isoptimus)s\r\nIs AMD Dynamic Switchable: %(isamddynamicswitchable)s\r\nCPU: %(cpu)s @ %(ghz).2f GHz (%(numcpu)s CPUs)\r\nMemory: %(mem)s MB (%(memfree)s MB available)' % {'triPlatform': trinity.platform, 497 'os': computerInfo.get('osMajorVersion', '-'), 498 'osminor': computerInfo.get('osMinorVersion', '-'), 499 'build': computerInfo.get('osBuild', '-'), 500 'servicepack': computerInfo.get('osPatch', '-'), 501 'videocard': computerInfo.get('videoCardAdapter', '-'), 502 'driverversion': computerInfo.get('videoDriverVersion', '-'), 503 'driverdate': computerInfo.get('videoDriverDate', '-'), 504 'drivervendor': computerInfo.get('videoDriverVendor', '-'), 505 'isoptimus': computerInfo.get('videoIsOptimus', '-'), 506 'isamddynamicswitchable': computerInfo.get('videoIsAmdDynamicSwitchable', '-'), 507 'cpu': computerInfo.get('cpuIdentifier', '-'), 508 'ghz': int(computerInfo.get('cpuMHz', 0)) / 1000.0, 509 'numcpu': computerInfo.get('cpuCount', '-'), 510 'mem': int(computerInfo.get('memoryPhysical', 0)) / 1024, 511 'memfree': int(ret.data.get('memoryFree', 0)) / 1024, 512 'bits': 'x%s' % computerInfo.get('clientBitCount', 32)} 513 return computer 514 515 def _GetSessionInfo(self, espUrl): 516 sessionInfo = ['Character: {charname} ({charid})'.format(charname=self.charName, charid=session.charid), 'Solar System: {solarsystemname} ({solarsystemid})'.format(solarsystemname=GetByMessageID(cfg.evelocations.Get(session.solarsystemid2).locationNameID, languageID='en-us'), solarsystemid=session.solarsystemid2)] 517 cohorts = [] 518 with ExceptionEater('Error getting cohorts on bugreport generation'): 519 cohorts = gatekeeper.character.GetCohorts() 520 if len(cohorts) > 0: 521 sessionInfo.append('Cohort IDs: {cohorts}'.format(cohorts=', '.join(map(str, cohorts)))) 522 if len(espUrl) > 0: 523 sessionInfo.append(espUrl) 524 return '\n'.join(sessionInfo) 525 526 def _GetEspUrl(self, ret): 527 espUrl = '' 528 if session.role & ROLE_GML > 0: 529 espUrl = 'ESP: http://%(serverEspUrl)s %(serverName)s' % {'serverName': ret.data['serverName'], 530 'serverEspUrl': ret.data['serverEspUrl']} 531 return espUrl 532 533 def GetCategories(self): 534 if self.categories is None: 535 self.categories = self.bugReporter.GetCategories() 536 return self.categories[:] 537 538 def GetCategoryName(self, categoryID): 539 for name, _categoryID in self.GetCategories(): 540 if _categoryID == categoryID: 541 return name 542 543 544 class BugReportingWindow(Window): 545 __guid__ = 'form.BugReportingWindow' 546 default_windowID = 'BugReportingWindow' 547 default_iconNum = 'res:/UI/Texture/WindowIcons/repairshop.png' 548 default_height = 700 549 default_minSize = (820, default_height) 550 default_captionLabelPath = 'UI/BugReporting/ReportBug' 551 552 def ApplyAttributes(self, attributes): 553 self.screenshotWindow = None 554 super(BugReportingWindow, self).ApplyAttributes(attributes) 555 self.RegisterPositionTraceKeyEvents() 556 self.bottomCont = ContainerAutoSize(parent=self.content, align=uiconst.TOBOTTOM, padding=const.defaultPadding) 557 self.ConstructTopCont() 558 self.ConstructBottomCont() 559 self.ConstructMainCont() 560 self.service = sm.GetService('bugReporting') 561 self.service.Init() 562 self.loadingWheel = LoadingWheel(parent=self.sr.maincontainer, align=uiconst.CENTER, width=64, height=64) 563 self.content.opacity = 0.0 564 uthread.new(self.FinishSettingUp) 565 566 def FinishSettingUp(self): 567 if not self.service.bugReporter.IsBugsServiceOnline(): 568 prompt_player_fsd_dialog('BugReportServerNotReachable') 569 self.Close() 570 return 571 if self.destroyed: 572 return 573 self.loadingWheel.Close() 574 self.UpdateScreenshotButtons() 575 self.PopulateCategoryCombo() 576 self.content.opacity = 1.0 577 578 def PopulateCategoryCombo(self): 579 categories = self.service.GetCategories() 580 initialOption = GetByLabel('UI/BugReporting/SelectCategory') 581 categories.insert(0, [initialOption, '']) 582 self.categoryCombo.LoadOptions(categories) 583 584 def HideAll(self): 585 self.Hide() 586 if self.screenshotWindow: 587 self.screenshotWindow.Close() 588 589 def GetScreenshot(self, n): 590 self.service.GetScreenshot(n) 591 592 def ConstructMainCont(self): 593 self.mainCont = Container(name='mainCont', parent=self.content, align=uiconst.TOALL) 594 descriptionCont = Container(name='descriptionCont', parent=self.mainCont, align=uiconst.TOTOP_PROP, height=0.5, padBottom=8) 595 reproStepsCont = Container(name='reproStepsCont', parent=self.mainCont, align=uiconst.TOALL, padTop=8) 596 EveLabelMedium(parent=descriptionCont, align=uiconst.TOTOP, padBottom=4, text=GetByLabel('UI/BugReporting/Description')) 597 self.descriptionEdit = EditPlainText(name='descriptionEdit', parent=descriptionCont, align=uiconst.TOALL, hintText=GetByLabel('UI/BugReporting/DescriptionHintText'), setvalue='', maxLength=2900) 598 EveLabelMedium(parent=reproStepsCont, align=uiconst.TOTOP, padBottom=4, text=GetByLabel('UI/BugReporting/ReproductionSteps')) 599 self.reproStepsEdit = EditPlainText(name='reproStepsEdit', parent=reproStepsCont, align=uiconst.TOALL, hintText=GetByLabel('UI/BugReporting/ReproductionStepsHintText'), setvalue='', maxLength=2900) 600 601 def ConstructTopCont(self): 602 top_cont = ContainerAutoSize(name='topCont', parent=self.content, align=uiconst.TOTOP, alignMode=uiconst.TOPLEFT, padBottom=16) 603 left_grid = LayoutGrid(parent=top_cont, align=uiconst.TOPLEFT, columns=2, cellSpacing=(8, 8)) 604 self.titleEdit = SingleLineEditText(name='titleEdit', parent=left_grid, align=uiconst.TOPLEFT, width=300, maxLength=250, hintText=GetByLabel('UI/BugReporting/Summary'), hint=GetByLabel('UI/BugReporting/SummaryHint')) 605 self.categoryCombo = Combo(name='categoryCombo', parent=left_grid, width=0, options=[]) 606 self.moreInfoIcon = MoreInfoIcon(name='moreInfo', parent=top_cont, align=uiconst.CENTERRIGHT) 607 self.moreInfoIcon.LoadTooltipPanel = self.LoadInfoTooltipPanel 608 609 def ConstructBottomCont(self): 610 buttonCont = Container(name='buttonCont', parent=self.bottomCont, align=uiconst.TOTOP, height=Button.default_height, top=16) 611 self.btns = ButtonGroup(parent=self.bottomCont, align=uiconst.TOTOP, btns=[(GetByLabel('UI/BugReporting/Send'), 612 self.ClickSend, 613 (), 614 84)], line=1) 615 self.editScreenshotButtons = [] 616 self.takeScreenshotButtons = [] 617 for i in xrange(NUM_SCREENSHOTS): 618 text = GetByLabel('UI/BugReporting/EditScreenshot', screenshotNum=i + 1) 619 c = Button(name='editscreenshot_%s' % i, parent=buttonCont, align=uiconst.TOLEFT, left=8 if i > 0 else 0, label=text, func=self.EditScreenshot) 620 self.editScreenshotButtons.append(c) 621 622 for i in xrange(NUM_SCREENSHOTS): 623 text = GetByLabel('UI/BugReporting/NewScreenshot', screenshotNum=i + 1) 624 c = Button(name='newscreenshot_%s' % i, parent=buttonCont, align=uiconst.TOLEFT, left=8 if i > 0 else 0, label=text, func=self.NewScreenshot, hint=GetByLabel('UI/BugReporting/NewScreenshotHint')) 625 self.takeScreenshotButtons.append(c) 626 627 self.saveToDiskButton = Button(label=GetByLabel('UI/BugReporting/Save'), parent=buttonCont, func=self.SaveToDisk, align=uiconst.TORIGHT, hint=GetByLabel('UI/BugReporting/SaveHint')) 628 self.loadFromDiskButton = Button(parent=buttonCont, align=uiconst.TORIGHT, left=8, label=GetByLabel('UI/BugReporting/Load'), func=self.LoadFromDisk, hint=GetByLabel('UI/BugReporting/LoadHint')) 629 630 def LoadInfoTooltipPanel(self, tooltipPanel, *args): 631 tooltipText = GetByLabel('UI/BugReporting/InfoTooltipText', url=BUG_GUIDE_URL) 632 tooltipPanel.state = uiconst.UI_NORMAL 633 tooltipPanel.LoadGeneric1ColumnTemplate() 634 tooltipPanel.AddLabelMedium(text=tooltipText, bold=True, state=uiconst.UI_NORMAL, wrapWidth=250) 635 636 def UpdateScreenshotButtons(self): 637 for i, c in enumerate(self.editScreenshotButtons): 638 c.state = uiconst.UI_HIDDEN 639 640 for i, c in enumerate(self.takeScreenshotButtons): 641 c.state = uiconst.UI_HIDDEN 642 643 i = -1 644 numScreenshots = len(self.service.screenshots) 645 for i in xrange(numScreenshots): 646 self.editScreenshotButtons[i].state = uiconst.UI_NORMAL 647 648 if i == -1: 649 self.takeScreenshotButtons[0].state = uiconst.UI_NORMAL 650 elif i < NUM_SCREENSHOTS - 1: 651 self.takeScreenshotButtons[i + 1].state = uiconst.UI_NORMAL 652 653 def EnableSendButton(self, enable): 654 try: 655 btn = self.btns.GetChild('SEND_Btn') 656 if enable: 657 btn.Enable() 658 else: 659 btn.Disable() 660 except: 661 pass 662 663 def ClickSend(self, *args): 664 uthread.new(self.DoClickSend) 665 666 def FormatString(self, txt): 667 txt = txt.replace('<br>', '\r\n') 668 return txt 669 670 def GetDataFromForm(self): 671 title = self.titleEdit.GetValue().strip() 672 description = self.descriptionEdit.GetValue(html=0).strip() 673 reproSteps = self.reproStepsEdit.GetValue(html=0).strip() 674 category = self.categoryCombo.GetValue() 675 description = self.FormatString(description) 676 reproSteps = self.FormatString(reproSteps) 677 data = {'Title': title, 678 'Description': description, 679 'ReproductionSteps': reproSteps, 680 'Category': category} 681 return data 682 683 def DoClickSend(self): 684 self.EnableSendButton(False) 685 data = self.GetDataFromForm() 686 if '' in (data['Title'], data['Description'], data['ReproductionSteps']) or not data['Category']: 687 self.EnableSendButton(True) 688 prompt_player_fsd_dialog('CustomInfo', {'info': GetByLabel('UI/BugReporting/AllFields')}) 689 return 690 data['ClientBuildNumber'] = boot.build 691 if len(self.service.screenshots) == 0: 692 ret = prompt_player_fsd_dialog('CustomQuestion', {'header': GetByLabel('UI/BugReporting/NoScreenshotsHeader'), 693 'question': GetByLabel('UI/BugReporting/NoScreenshotsMessage')}, uiconst.YESNO) 694 if ret == uiconst.ID_YES: 695 self.EnableSendButton(True) 696 uthread.new(self.GetScreenshot, None) 697 return 698 if ret in (uiconst.ID_CANCEL, uiconst.ID_CLOSE): 699 self.EnableSendButton(True) 700 return 701 result = False 702 bugreportID = '' 703 attachments = False 704 self.loadingWheel = LoadingWheel(parent=self.content, align=uiconst.CENTER, width=64, height=64) 705 self.mainCont.opacity = 0.3 706 try: 707 result = self.service.SendDefectOrBugReport(data) 708 except SendAttachmentError: 709 log.LogException() 710 attachments = False 711 result = True 712 except Exception as e: 713 log.LogException() 714 self.EnableSendButton(True) 715 self.mainCont.opacity = 1.0 716 self.loadingWheel.Close() 717 prompt_player_fsd_dialog('CustomInfo', {'info': GetByLabel('UI/BugReporting/SendError', errorMessage=e)}) 718 else: 719 bugreportID = result['key'] 720 attachments = True 721 722 if result: 723 self.Close() 724 self.MessageSuccess(attachments, bugreportID) 725 else: 726 self.EnableSendButton(True) 727 728 def MessageSuccess(self, attachments, bugreportID): 729 header = GetByLabel('UI/BugReporting/ReportSentHeader') 730 if attachments: 731 msg = GetByLabel('UI/BugReporting/ReportSent', bugreportID=bugreportID) 732 else: 733 msg = GetByLabel('UI/BugReporting/ReportSentAttFail') 734 prompt_player_fsd_dialog('CustomWarning', {'header': header, 735 'warning': msg}) 736 737 def OpenExternalBugs(self, *args): 738 header = GetByLabel('UI/BugReporting/OpenBugsExternalHeader') 739 question = GetByLabel('UI/BugReporting/OpenBugsExternal') 740 if prompt_player_fsd_dialog('CustomQuestion', {'header': header, 741 'question': question}, uiconst.YESNO, modal=False) == uiconst.ID_YES: 742 webbrowser.open_new(BUG_REPORTS_URL) 743 744 def NewScreenshot(self, button): 745 n = int(button.name.split('_')[1]) 746 self.GetScreenshot(n) 747 748 def EditScreenshot(self, button): 749 n = int(button.name.split('_')[1]) 750 self.DoEditScreenshot(n) 751 752 def DoEditScreenshot(self, n): 753 self.screenshotWindow = ScreenshotEditingWnd.Open(n=n, service=self.service) 754 755 def ClearLogs(self, *args): 756 sm.GetService('monitor').ClearLogInMemory() 757 758 def ViewLogs(self, *args): 759 sm.GetService('monitor').ShowLogTab() 760 761 def CopyException(self, *args): 762 logs = blue.logInMemory.GetEntries() 763 lastException = '' 764 for l in logs: 765 if 'EXCEPTION END' in l[4]: 766 lastException = l[4] 767 break 768 769 if len(lastException): 770 inf = 'Most recent exception has been copied to the clipboard' 771 lastException = lastException.replace('\n', '\r\n') 772 lastException = lastException.replace('<', '<') 773 blue.pyos.SetClipboardData(lastException) 774 else: 775 inf = 'No exceptions in the logs' 776 prompt_player_fsd_dialog('CustomNotify', {'notify': inf}) 777 778 def SaveToDisk(self, *args): 779 data = self.GetDataFromForm() 780 self.service.SaveToDisk(data) 781 prompt_player_fsd_dialog('CustomNotify', {'notify': GetByLabel('UI/BugReporting/SavedToDisk')}) 782 783 def LoadFromDisk(self, *args): 784 ret = prompt_player_fsd_dialog('CustomQuestion', {'header': GetByLabel('UI/BugReporting/LoadFromDiskHeader'), 785 'question': GetByLabel('UI/BugReporting/LoadFromDisk')}, uiconst.YESNO) 786 if ret != uiconst.ID_YES: 787 return 788 data = self.service.LoadFromDisk() 789 self.titleEdit.SetValue(data.get('Title', '')) 790 self.descriptionEdit.SetValue(data.get('Description', '')) 791 self.reproStepsEdit.SetValue(data.get('ReproductionSteps', '')) 792 self.categoryCombo.SelectItemByValue(data.get('Category', None)) 793 self.UpdateScreenshotButtons() 794 795 def RegisterPositionTraceKeyEvents(self): 796 self.keyDownCookie = uicore.event.RegisterForTriuiEvents(uiconst.UI_KEYDOWN, self.OnGlobalKeyDownCallback) 797 798 def OnGlobalKeyDownCallback(self, wnd, eventID, param): 799 vkey, flag = param 800 if self.destroyed: 801 return False 802 ctrl = uicore.uilib.Key(uiconst.VK_CONTROL) 803 shift = uicore.uilib.Key(uiconst.VK_SHIFT) 804 alt = uicore.uilib.Key(uiconst.VK_MENU) 805 p = uicore.uilib.Key(uiconst.VK_P) 806 if ctrl and alt and shift and p: 807 uthread.new(self.GetScreenshot, None) 808 return True 809 810 811 class ScreenshotEditingWnd(Window): 812 __guid__ = 'form.ScreenshotEditingWnd' 813 default_windowID = 'ScreenshotEditingWnd' 814 815 def ApplyAttributes(self, attributes): 816 Window.ApplyAttributes(self, attributes) 817 self.SetCaption(GetByLabel('UI/BugReporting/ScreenshotEditor')) 818 self.MakeUnstackable() 819 self.service = attributes['service'] 820 self.n = attributes['n'] 821 self.screenshotPath = sm.GetService('bugReporting').GetScreenshotPath(self.n) 822 self.service.LogInfo('Screenshot editing window using screenshot from', self.screenshotPath) 823 w = h = 0 824 try: 825 surface = trinity.Tr2HostBitmap() 826 surface.CreateFromFile(self.screenshotPath) 827 w, h = surface.width, surface.height 828 except: 829 log.LogException() 830 831 self.screenshotWidth = w or uicore.desktop.width 832 self.screenshotHeight = h or uicore.desktop.height 833 self.screenShotScaling = None 834 maxWidth = max(self.screenshotWidth / 2, 955) 835 self.SetMinSize([maxWidth, self.screenshotHeight / 2]) 836 topCont = Container(name='topCont', parent=self.content, align=uiconst.TOTOP, height=30, padding=const.defaultPadding) 837 self.layoutAreaParent = Container(parent=self.content, padding=10) 838 self.layoutAreaParent._OnSizeChange_NoBlock = self.RecalcLayout 839 self.layoutArea = Container(parent=self.layoutAreaParent, align=uiconst.CENTER, width=self.screenshotWidth / 4, height=self.screenshotHeight / 4) 840 self.overlays = Container(name='overlays', parent=self.layoutArea, align=uiconst.TOALL, clipChildren=True) 841 self.screenshotSprite = Sprite(name='screenshotSprite', parent=self.layoutArea, texturePath=self.screenshotPath, align=uiconst.TOALL, padding=0) 842 self.screenshotSprite.ReloadTexture() 843 Button(label=GetByLabel('UI/BugReporting/ScreenshotSave'), parent=topCont, func=self.SaveScreenshot, left=8, align=uiconst.CENTERRIGHT) 844 fb = Button(label=GetByLabel('UI/BugReporting/ScreenshotAddFrame'), func=self.AddFrame, parent=topCont, left=8, align=uiconst.CENTERLEFT) 845 cfb = Button(label=GetByLabel('UI/BugReporting/ScreenshotAddCropFrame'), func=self.AddCropFrame, parent=topCont, left=fb.left + fb.width + 8, align=uiconst.CENTERLEFT) 846 la = Button(label=GetByLabel('UI/BugReporting/ScreenshotAddLeftArrow'), func=self.AddLeftArrowFrame, parent=topCont, left=cfb.left + cfb.width + 8, align=uiconst.CENTERLEFT) 847 ra = Button(label=GetByLabel('UI/BugReporting/ScreenshotAddRightArrow'), func=self.AddRightArrowFrame, parent=topCont, left=la.left + la.width + 8, align=uiconst.CENTERLEFT) 848 tc = Button(label=GetByLabel('UI/BugReporting/ScreenshotAddText'), func=self.AddTextFrame, parent=topCont, left=ra.left + ra.width + 8, align=uiconst.CENTERLEFT) 849 cb = Button(label=GetByLabel('UI/BugReporting/ScreenshotButtonNew'), func=self.NewScreenshot, parent=topCont, left=tc.left + tc.width + 20, align=uiconst.CENTERLEFT) 850 cb = Button(label=GetByLabel('UI/BugReporting/ScreenshotDelete'), func=self.DeleteScreenshot, parent=topCont, left=cb.left + cb.width + 8, align=uiconst.CENTERLEFT) 851 self.UpdateLayoutArea() 852 853 def UpdateLayoutArea(self): 854 prevScaling = self.screenShotScaling 855 areaWidth, areaHeight = self.layoutAreaParent.GetAbsoluteSize() 856 xFitScale = areaWidth / float(self.screenshotWidth) 857 yFitScale = areaHeight / float(self.screenshotHeight) 858 self.screenShotScaling = scaling = min(xFitScale, yFitScale) 859 self.layoutArea.width = int(self.screenshotWidth * scaling) 860 self.layoutArea.height = int(self.screenshotHeight * scaling) 861 if prevScaling and prevScaling != scaling: 862 for overlay in self.overlays.children: 863 overlay.UpdateProportionalPosition(scaling) 864 865 def NewScreenshot(self, *args): 866 self.service.GetScreenshot(self.n) 867 868 def DeleteScreenshot(self, *args): 869 self.service.DeleteScreenshot(self.n) 870 self.Close() 871 872 def AddTextFrame(self, *args): 873 initWidth = int(200 * self.screenShotScaling) 874 initHeight = int(32 * self.screenShotScaling) 875 initLeft = int(self.screenshotWidth * 0.5 * self.screenShotScaling) - initWidth / 2 876 initTop = int(self.screenshotHeight * 0.5 * self.screenShotScaling) - initHeight / 2 877 MoveableTextRect(name='Text', parent=self.overlays, pos=(initLeft, 878 initTop, 879 initWidth, 880 initHeight), idx=0, showControls=True, scaling=self.screenShotScaling) 881 882 def AddRightArrowFrame(self, *args): 883 initWidth = int(ARROW_HEIGHT * 2 * self.screenShotScaling) 884 initHeight = int(ARROW_HEIGHT * self.screenShotScaling) 885 initLeft = int(self.screenshotWidth * 0.5 * self.screenShotScaling) 886 initTop = int(self.screenshotHeight * 0.5 * self.screenShotScaling) 887 container = MoveableRect(name='Arrow', parent=self.overlays, pos=(initLeft, 888 initTop, 889 initWidth, 890 initHeight), showControls=False, idx=0) 891 icon = Sprite(label='arrow', parent=container, texturePath=RIGHT_ARROW, align=uiconst.TOALL, state=uiconst.UI_DISABLED) 892 container.AddColorObject(icon) 893 container.ChangeColor(COLOR_GREEN) 894 895 def AddLeftArrowFrame(self, *args): 896 initWidth = int(ARROW_HEIGHT * 2 * self.screenShotScaling) 897 initHeight = int(ARROW_HEIGHT * self.screenShotScaling) 898 initLeft = int(self.screenshotWidth * 0.5 * self.screenShotScaling) 899 initTop = int(self.screenshotHeight * 0.5 * self.screenShotScaling) 900 container = MoveableRect(name='Arrow', parent=self.overlays, pos=(initLeft, 901 initTop, 902 initWidth, 903 initHeight), showControls=False, idx=0) 904 icon = Sprite(label='arrow', parent=container, texturePath=LEFT_ARROW, align=uiconst.TOALL, state=uiconst.UI_DISABLED) 905 container.AddColorObject(icon) 906 container.ChangeColor(COLOR_GREEN) 907 908 def AddCropFrame(self, *args): 909 for each in self.overlays.children[:]: 910 if each.name == 'cropFrame': 911 each.Close() 912 913 initLeft = int(self.screenshotWidth * 0.25 * self.screenShotScaling) 914 initTop = int(self.screenshotHeight * 0.25 * self.screenShotScaling) 915 initWidth = int(self.screenshotWidth * 0.5 * self.screenShotScaling) 916 initHeight = int(self.screenshotHeight * 0.5 * self.screenShotScaling) 917 MoveableRect(name='Crop', parent=self.overlays, pos=(initLeft, 918 initTop, 919 initWidth, 920 initHeight), maskColor=(0, 0, 0, 0.75), showControls=True) 921 922 def AddFrame(self, *args): 923 initLeft = int((self.screenshotWidth - 200) * self.screenShotScaling) / 2 924 initTop = int((self.screenshotHeight - 200) * self.screenShotScaling) / 2 925 initWidth = int(200 * self.screenShotScaling) 926 initHeight = int(200 * self.screenShotScaling) 927 MoveableRect(parent=self.overlays, name='Frame', pos=(initLeft, 928 initTop, 929 initWidth, 930 initHeight), showFrame=True, showControls=True, idx=0) 931 932 def Clear(self, *args): 933 self.overlays.Flush() 934 935 def SaveScreenshot(self, *args): 936 self.Hide() 937 baseTexture = trinity.Tr2RenderTarget(self.screenshotWidth, self.screenshotHeight, 1, trinity.PIXEL_FORMAT.B8G8R8A8_UNORM) 938 renderJob = trinity.CreateRenderJob() 939 desktop = UIRoot(name='screenshot', width=self.screenshotWidth, height=self.screenshotHeight, renderTarget=baseTexture, renderJob=renderJob) 940 cropProportion = None 941 for each in self.overlays.children: 942 if each.name == 'Crop': 943 cropProportion = each.proportionalPosition 944 945 self.overlays.SetParent(desktop) 946 self.screenshotSprite.SetParent(desktop) 947 for overlay in self.overlays.children: 948 overlay.UpdateProportionalPosition(1.0) 949 950 desktop.UpdateAlignment() 951 blue.pyos.synchro.Yield() 952 renderJob.ScheduleOnce() 953 renderJob.WaitForFinish() 954 blue.pyos.synchro.Yield() 955 path = AbsPath(self.screenshotPath) 956 if cropProportion: 957 cl, cr, ct, cb = cropProportion 958 cr = min(1.0, cr) 959 cb = min(1.0, cb) 960 bmp = trinity.Tr2HostBitmap(baseTexture) 961 bmp.Crop(int(cl * self.screenshotWidth), int(ct * self.screenshotHeight), int(cr * self.screenshotWidth), int(cb * self.screenshotHeight)) 962 bmp.Save(path) 963 else: 964 trinity.Tr2HostBitmap(baseTexture).Save(path) 965 desktop.Close() 966 self.CloseByUser() 967 968 def OnScale_(self, *args): 969 pass 970 971 def RecalcLayout(self, *args): 972 self.UpdateLayoutArea() 973 974 975 class MoveableRect(Container): 976 __guid__ = 'MoveableRect' 977 default_align = uiconst.TOPLEFT 978 default_state = uiconst.UI_NORMAL 979 CONTROLS = (('topleft', uiconst.TOPLEFT), 980 ('topright', uiconst.TOPRIGHT), 981 ('bottomright', uiconst.BOTTOMRIGHT), 982 ('bottomleft', uiconst.BOTTOMLEFT)) 983 984 def ApplyAttributes(self, attributes): 985 Container.ApplyAttributes(self, attributes) 986 self.modifyThread = None 987 self.frame = None 988 self.colorObjects = [] 989 self.color = COLOR_GREEN 990 self.controls = Container(parent=self, state=uiconst.UI_PICKCHILDREN) 991 if attributes.showControls: 992 for cornerName, align in self.CONTROLS: 993 scalePoint = Fill(parent=self.controls, align=align, color=(1, 1, 1, 1), pos=(0, 0, 10, 10), state=uiconst.UI_NORMAL, name=cornerName) 994 scalePoint.OnMouseDown = (self.StartScaling, scalePoint) 995 scalePoint.OnMouseUp = (self.EndScaling, scalePoint) 996 self.AddColorObject(scalePoint) 997 998 self.maskColor = attributes.maskColor 999 if self.maskColor: 1000 self.UpdateMask() 1001 if attributes.showFrame: 1002 self.frame = Frame(parent=self, frameConst=uiconst.FRAME_BORDER2_CORNER3, color=(1.0, 1.0, 1.0, 0.5)) 1003 self.AddColorObject(self.frame) 1004 self.UpdateColor() 1005 self.RegisterProportionalValues() 1006 1007 def AddColorObject(self, obj): 1008 self.colorObjects.append(obj) 1009 self.UpdateColor() 1010 1011 def StartScaling(self, obj, *args): 1012 startProps = (obj.name, 1013 uicore.uilib.x, 1014 uicore.uilib.y, 1015 self.left, 1016 self.top, 1017 self.width, 1018 self.height) 1019 self.modifyThread = timerstuff.AutoTimer(1, self.ModifyRect, startProps) 1020 1021 def EndScaling(self, obj, *args): 1022 self.modifyThread = None 1023 1024 def OnMouseDown(self, *args): 1025 startProps = ('move', 1026 uicore.uilib.x, 1027 uicore.uilib.y, 1028 self.left, 1029 self.top, 1030 self.width, 1031 self.height) 1032 self.modifyThread = timerstuff.AutoTimer(1, self.ModifyRect, startProps) 1033 1034 def OnMouseUp(self, *args): 1035 self.modifyThread = None 1036 1037 def ModifyRect(self, startProps, *args): 1038 side, cursorX, cursorY, l, t, w, h = startProps 1039 dx = uicore.uilib.x - cursorX 1040 dy = uicore.uilib.y - cursorY 1041 if side == 'move': 1042 self.left = max(0, l + dx) 1043 self.top = max(0, t + dy) 1044 else: 1045 if 'left' in side: 1046 self.width = max(16, w - dx) 1047 self.left = max(0, min(l + dx, l + w - 16)) 1048 else: 1049 self.width = max(16, w + dx) 1050 if 'top' in side: 1051 self.height = max(16, h - dy) 1052 self.top = max(0, min(t + dy, t + h - 16)) 1053 else: 1054 self.height = max(16, h + dy) 1055 if self.maskColor: 1056 self.UpdateMask() 1057 self.RegisterProportionalValues() 1058 1059 def RegisterProportionalValues(self): 1060 parWidth, parHeight = self.parent.GetAbsoluteSize() 1061 pl = self.left / float(parWidth) 1062 pr = (self.left + self.width) / float(parWidth) 1063 pt = self.top / float(parHeight) 1064 pb = (self.top + self.height) / float(parHeight) 1065 self.proportionalPosition = (pl, 1066 pr, 1067 pt, 1068 pb) 1069 1070 def UpdateProportionalPosition(self, newScaling): 1071 parWidth, parHeight = self.parent.GetAbsoluteSize() 1072 pl, pr, pt, pb = self.proportionalPosition 1073 self.left = int(pl * parWidth) 1074 self.top = int(pt * parHeight) 1075 self.width = int((pr - pl) * parWidth) 1076 self.height = int((pb - pt) * parHeight) 1077 if newScaling == 1.0 and getattr(self, 'numLabel', None): 1078 self.numLabel.useSizeFromTexture = True 1079 self.numLabel.Layout() 1080 self.numLabel.top = -self.numLabel.textheight - 2 1081 if self.maskColor: 1082 self.UpdateMask() 1083 1084 def UpdateMask(self): 1085 maskFills = getattr(self, 'maskFills', None) 1086 if not maskFills: 1087 self.maskFills = [] 1088 for i in xrange(4): 1089 self.maskFills.append(Fill(parent=self, color=self.maskColor, align=uiconst.TOPLEFT)) 1090 1091 self.maskFills[0].top = -uicore.desktop.height 1092 self.maskFills[0].height = uicore.desktop.height 1093 self.maskFills[0].left = -uicore.desktop.width 1094 self.maskFills[0].width = uicore.desktop.width * 2 1095 self.maskFills[1].top = self.height 1096 self.maskFills[1].height = uicore.desktop.height 1097 self.maskFills[1].left = -uicore.desktop.width 1098 self.maskFills[1].width = uicore.desktop.width * 2 1099 self.maskFills[2].top = 0 1100 self.maskFills[2].height = self.height 1101 self.maskFills[2].left = -uicore.desktop.width 1102 self.maskFills[2].width = uicore.desktop.width 1103 self.maskFills[3].top = 0 1104 self.maskFills[3].height = self.height 1105 self.maskFills[3].left = self.width 1106 self.maskFills[3].width = uicore.desktop.width 1107 1108 def OnMouseEnter(self, *args): 1109 self.ShowControls() 1110 self.mouseOverThread = timerstuff.AutoTimer(1, self.CheckMouseOver) 1111 1112 def CheckMouseOver(self, *args): 1113 if self.destroyed: 1114 self.mouseOverThread = None 1115 return 1116 mo = uicore.uilib.mouseOver 1117 if mo is self or mo.IsUnder(self) or self.modifyThread: 1118 return 1119 self.mouseOverThread = None 1120 self.HideControls() 1121 1122 def ShowControls(self): 1123 self.controls.Show() 1124 1125 def HideControls(self): 1126 self.controls.Hide() 1127 1128 def Numberate(self, *args): 1129 currentNumbers = [] 1130 for each in self.parent.children: 1131 autoNumber = getattr(each, 'autoNumber', None) 1132 if autoNumber is not None: 1133 currentNumbers.append(autoNumber) 1134 1135 tryNum = 1 1136 while tryNum in currentNumbers: 1137 tryNum += 1 1138 1139 self.AssignNumber(tryNum) 1140 1141 def AssignNumber(self, number): 1142 if getattr(self, 'numLabel', None) is None: 1143 self.numLabel = Label(parent=self, fontsize=NUMFONTSIZE, align=uiconst.TOPRIGHT, bold=1) 1144 self.numLabel.useSizeFromTexture = False 1145 self.numLabel._OnSizeChange_NoBlock = self.OnNumberSizeChange 1146 self.autoNumber = number 1147 self.numLabel.text = unicode(number) 1148 self.AddColorObject(self.numLabel) 1149 1150 def OnNumberSizeChange(self, *args): 1151 screenShotWindow = GetWindowAbove(self) 1152 if screenShotWindow: 1153 self.numLabel.displayWidth = self.numLabel.textwidth * screenShotWindow.screenShotScaling 1154 self.numLabel.displayHeight = self.numLabel.textheight * screenShotWindow.screenShotScaling 1155 self.numLabel.top = -self.ReverseScaleDpi((self.numLabel.textheight + 2) * screenShotWindow.screenShotScaling) 1156 1157 def GetMenu(self, *args): 1158 m = [(GetByLabel('UI/BugReporting/ScreenshotDeleteObject', object=self.name), self.Delete), (GetByLabel('UI/BugReporting/ScreenshotNumberate'), self.Numberate)] 1159 if self.maskColor: 1160 return m 1161 m = m + [None, 1162 (GetByLabel('UI/BugReporting/ScreenshotRed'), self.ChangeColor, ((1, 0, 0, 1),)), 1163 (GetByLabel('UI/BugReporting/ScreenshotGreen'), self.ChangeColor, ((0, 1, 0, 1),)), 1164 (GetByLabel('UI/BugReporting/ScreenshotBlue'), self.ChangeColor, ((0, 0, 1, 1),))] 1165 return m 1166 1167 def Delete(self, *args): 1168 uthread.new(self.Close) 1169 1170 def UpdateColor(self): 1171 self.ChangeColor(self.color) 1172 1173 def ChangeColor(self, color): 1174 self.color = color 1175 for each in self.colorObjects: 1176 each.SetRGBA(*color) 1177 1178 1179 class MoveableTextRect(MoveableRect): 1180 __guid__ = 'MoveableTextRect' 1181 DEFAULTTEXT = GetByLabel('UI/BugReporting/ScreenshotDefaultText') 1182 CONTROLS = () 1183 1184 def ApplyAttributes(self, attributes): 1185 MoveableRect.ApplyAttributes(self, attributes) 1186 self.originalScaling = attributes.get('scaling', 1.0) 1187 self.textEdit = None 1188 self.underlay = Fill(parent=self) 1189 self.sampleText = Label(parent=self, left=8, top=8, text=self.DEFAULTTEXT, fontsize=BASEFONTSIZE) 1190 self.sampleText.useSizeFromTexture = False 1191 self.sampleText._OnSizeChange_NoBlock = self.OnTextSizeChange 1192 self.UpdateColor() 1193 self.OnTextSizeChange() 1194 1195 def OnTextSizeChange(self, *args): 1196 screenShotWindow = GetWindowAbove(self) 1197 if screenShotWindow: 1198 self.sampleText.displayWidth = self.sampleText.textwidth * screenShotWindow.screenShotScaling 1199 self.sampleText.displayHeight = self.sampleText.textheight * screenShotWindow.screenShotScaling 1200 self.width = self.ReverseScaleDpi(self.sampleText.displayWidth) + 16 1201 self.height = self.ReverseScaleDpi(self.sampleText.displayHeight) + 16 1202 1203 def OnDblClick(self, *args): 1204 self.sampleText.Hide() 1205 setValue = '' 1206 if self.sampleText.text != self.DEFAULTTEXT: 1207 setValue = self.sampleText.text 1208 self.width = 200 1209 self.height = 100 1210 self.textEdit = EditPlainText(parent=self, maxLength=1000, align=uiconst.TOALL, idx=0, pos=(0, 0, 0, 0)) 1211 self.textEdit.HideBackground() 1212 self.textEdit.RemoveActiveFrame() 1213 uicore.registry.SetFocus(self.textEdit) 1214 self.textEdit.SetValue(setValue, cursorPos=len(setValue)) 1215 self.sr.cookie = uicore.event.RegisterForTriuiEvents(uiconst.UI_MOUSEDOWN, self.OnGlobalMouseDown) 1216 1217 def OnGlobalMouseDown(self, *args): 1218 mo = uicore.uilib.mouseOver 1219 if mo is self or mo.IsUnder(self): 1220 return True 1221 if self.textEdit: 1222 val = self.textEdit.GetValue() 1223 self.sampleText.Show() 1224 self.sampleText.text = val or self.DEFAULTTEXT 1225 self.textEdit.Close() 1226 self.textEdit = None 1227 return False 1228 1229 def ChangeColor(self, color): 1230 MoveableRect.ChangeColor(self, color) 1231 if getattr(self, 'sampleText', None): 1232 self.sampleText.SetTextColor(color) 1233 self.underlay.SetRGBA(*color) 1234 self.underlay.opacity = 0.1 1235 1236 def UpdateProportionalPosition(self, newScaling): 1237 parWidth, parHeight = self.parent.GetAbsoluteSize() 1238 pl, pr, pt, pb = self.proportionalPosition 1239 self.left = int(pl * parWidth) 1240 self.top = int(pt * parHeight) 1241 self.sampleText.fontsize = int(BASEFONTSIZE * newScaling / self.originalScaling) 1242 if newScaling == 1.0: 1243 self.sampleText.useSizeFromTexture = True 1244 self.sampleText.Layout() 1245 self.width = self.sampleText.textwidth + 16 1246 self.height = self.sampleText.textheight + 16 1247 1248 1249 def GetServerVersion(serverBuild, serverVersion): 1250 return '%s - %s.%s' % (boot.codename, serverVersion, serverBuild)