NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

contracts.py (46739B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\shared\neocom\contracts\contracts.py
      2 import blue
      3 import carbonui.const as uiconst
      4 import eve.common.script.util.notificationconst as notificationConst
      5 import eveformat.client
      6 import evetypes
      7 import localization
      8 import structures
      9 import uthread
     10 import utillib
     11 from caching.memoize import Memoize
     12 from carbon.common.script.sys.service import Service
     13 from carbon.common.script.util.format import FmtDate
     14 from player_messaging.client.ui_message import message_player, prompt_player_fsd_dialog
     15 from eve.client.script.ui.shared.neocom.contracts.contractsDetailsWnd import ContractDetailsWindow
     16 from eve.client.script.ui.shared.neocom.contracts.contractsWnd import ContractsWindow
     17 from eve.client.script.ui.shared.neocom.contracts.createContractWnd import CreateContractWnd
     18 from eve.client.script.ui.shared.neocom.contracts.ignoreListWnd import IgnoreListWindow
     19 from eve.client.script.ui.shared.neocom.contracts.multiContractsItemExchangeWnd import MultiContractsItemExchangeWnd
     20 from eve.client.script.ui.util import uix
     21 from eve.client.script.util.contractutils import FmtISKWithDescription, GetMarketTypes
     22 from eve.common.lib import appConst as const
     23 from eve.common.script.sys import idCheckers
     24 from eve.common.script.util.contractscommon import CONTYPE_AUCTIONANDITEMECHANGE, GetContractTitle, MAX_AMOUNT, MAX_NUM_CONTRACTS, MINUTE, NUM_CONTRACTS_BY_SKILL, NUM_CONTRACTS_BY_ADVSKILL, NUM_CONTRACTS_BY_SKILL_CORP, const_conCourierWarningVolume, CanRequestType
     25 from eveexceptions import UserError
     26 from eveformat.client.location import get_security_status
     27 from eveservices.menu import GetMenuService
     28 from inventorycommon.const import typeContracting, typeCorporationContracting, typeAdvancedContracting
     29 from locks import TempLock
     30 from menu import MenuLabel
     31 from notifications.common.formatters.contractAssigned import ContractAssignedFormatter
     32 from notifications.common.formatters.contractAttention import ContractNeedsAttentionFormatter
     33 from playerprogression.contractprogressionnotifier import ContractProgressionNotifier
     34 from security.client.securityColor import get_security_status_color
     35 MAX_IGNORED = 1000
     36 CACHE_TIME = 10
     37 
     38 def GetSecurityClassText(securityClass):
     39     txt = {const.securityClassZeroSec: '<color=0xffbb0000>%s</color>' % localization.GetByLabel('UI/Common/NullSec'),
     40      const.securityClassLowSec: '<color=0xffbbbb00>%s</color>' % localization.GetByLabel('UI/Common/LowSec'),
     41      const.securityClassHighSec: '<color=0xff00bb00>%s</color>' % localization.GetByLabel('UI/Common/HighSec'),
     42      const.securityClassSafeSec: '<color=0xff0000bb>%s</color>' % localization.GetByLabel('UI/Common/SafeSec')}.get(securityClass, localization.GetByLabel('UI/Contracts/ContractsWindow/UnknownSystem'))
     43     return txt
     44 
     45 
     46 class ContractsSvc(Service):
     47     __exportedcalls__ = {'GetContract': {},
     48      'AcceptContract': {},
     49      'DeleteContract': {},
     50      'CompleteContract': {},
     51      'FailContract': {},
     52      'RejectContract': {},
     53      'CreateContract': {},
     54      'PlaceBid': {},
     55      'FinishAuction': {},
     56      'GetMarketTypes': {},
     57      'NumOutstandingContracts': {},
     58      'GetSolarSystemIDForStationOrStructure': {},
     59      'GetItemsInContainer': {},
     60      'GetItemsInDockableLocation': {},
     61      'DeleteNotification': {},
     62      'CollectMyPageInfo': {},
     63      'ClearCache': {},
     64      'AddIgnore': {},
     65      'GetMessages': {},
     66      'DeliverCourierContractFromItemID': {},
     67      'FindCourierContractFromItemID': {}}
     68     __guid__ = 'svc.contracts'
     69     __notifyevents__ = ['ProcessSessionChange',
     70      'OnContractAssigned',
     71      'OnContractCompleted',
     72      'OnContractAccepted',
     73      'OnContractRejected',
     74      'OnContractFailed',
     75      'OnContractOutbid',
     76      'OnContractBuyout',
     77      'OnContractCreated',
     78      'OnNotificationUiReady',
     79      'OnSessionReset',
     80      'OnShowContract']
     81     __servicename__ = 'Contracts'
     82     __displayname__ = 'Contracts Service'
     83     __dependencies__ = []
     84     __update_on_reload__ = 0
     85 
     86     def __init__(self):
     87         Service.__init__(self)
     88         self.ReInitialize()
     89 
     90     def ReInitialize(self):
     91         self.detailsByRegionByTypeFlag = {}
     92         self.contractID = None
     93         self.markettypes = None
     94         self.contracts = {}
     95         self.myPageInfo = None
     96         self.myExpiredContractList = None
     97         self.contractsInProgress = {}
     98         self.messages = []
     99         self.inited = False
    100         self.contractTypeIDFinder = None
    101         self.SetupContractProgressionNotifier()
    102 
    103     def SetupContractProgressionNotifier(self):
    104         self.contractProgressionNotifier = ContractProgressionNotifier(get_contract_escrow_function=self.GetMyContractEscrow)
    105         self.SubscribeContractProgressWatcher = self.contractProgressionNotifier.register_subscriber
    106         self.UnsubscribeContractProgressWatcher = self.contractProgressionNotifier.unregister_subscriber
    107 
    108     def Run(self, memStream = None):
    109         self.contractProxySvc = None
    110 
    111     def GetContractProxySvc(self):
    112         if self.contractProxySvc == None:
    113             self.contractProxySvc = sm.ProxySvc('contractProxy')
    114         return self.contractProxySvc
    115 
    116     def Stop(self, memStream = None):
    117         ContractsWindow.CloseIfOpen()
    118 
    119     def Init(self):
    120         if session.charid and not self.inited:
    121             self.inited = True
    122 
    123     def GetContractsInProgress(self):
    124         return self.contractsInProgress
    125 
    126     def GetContractsBookmarkMenu(self):
    127         contractsMenu = []
    128         stationsPickup = set()
    129         stationsDropoff = set()
    130         for contractID, contract in self.contractsInProgress.iteritems():
    131             if contract[-1] < blue.os.GetWallclockTime():
    132                 continue
    133             if cfg.evelocations.Get(contract[0]).solarSystemID == session.solarsystemid2:
    134                 stationsPickup.add(contract[0])
    135             if cfg.evelocations.Get(contract[1]).solarSystemID == session.solarsystemid2:
    136                 stationsDropoff.add(contract[1])
    137 
    138         for stationID in stationsPickup:
    139             pickupLabel = localization.GetByLabel('UI/Contracts/ContractsService/CourierPickup', station=stationID)
    140             contractsMenu.append((pickupLabel, ('isDynamic', GetMenuService().CelestialMenu, (stationID,))))
    141 
    142         for stationID in stationsDropoff:
    143             dropoffLabel = localization.GetByLabel('UI/Contracts/ContractsService/CourierDelivery', station=stationID)
    144             contractsMenu.append((dropoffLabel, ('isDynamic', GetMenuService().CelestialMenu, (stationID,))))
    145 
    146         return contractsMenu
    147 
    148     def FindRelated(self, typeID, groupID, categoryID, issuerID, locationID, endLocationID, contractType = None, reset = True, *args):
    149         if contractType != const.conTypeCourier:
    150             contractType = CONTYPE_AUCTIONANDITEMECHANGE
    151         self.OpenSearchTab(typeID, groupID, categoryID, issuerID, locationID, endLocationID, const.conAvailPublic, contractType, reset)
    152 
    153     def OpenAssignedToMe(self, contractType = CONTYPE_AUCTIONANDITEMECHANGE, isCorp = False):
    154         self.Show()
    155         wnd = ContractsWindow.GetIfOpen()
    156         if wnd is not None:
    157             wnd = ContractsWindow.Open()
    158             blue.pyos.synchro.SleepWallclock(10)
    159             wnd.tabGroup.SelectByIdx(2)
    160             wnd.contractSearchContent.FindMyContracts(contractType, isCorp)
    161 
    162     def GetRelatedMenu(self, contract, typeID, reset = True):
    163         m = []
    164         startConstellationID = sm.GetService('map').GetItem(contract.startSolarSystemID).locationID
    165         if typeID and contract.type != const.conTypeCourier:
    166             m += [(MenuLabel('UI/Contracts/ContractsWindow/SameType'), self.FindRelated, (typeID,
    167                None,
    168                None,
    169                None,
    170                None,
    171                None,
    172                contract.type,
    173                reset))]
    174             m += [(MenuLabel('UI/Contracts/ContractsWindow/SameGroup'), self.FindRelated, (None,
    175                evetypes.GetGroupID(typeID),
    176                None,
    177                None,
    178                None,
    179                None,
    180                contract.type,
    181                reset))]
    182             m += [(MenuLabel('UI/Contracts/ContractsWindow/SameCategory'), self.FindRelated, (None,
    183                None,
    184                evetypes.GetCategoryID(typeID),
    185                None,
    186                None,
    187                None,
    188                contract.type,
    189                reset))]
    190             m += [None]
    191         m += [(MenuLabel('UI/Contracts/ContractsWindow/FromSameIssuer'), self.FindRelated, (None,
    192            None,
    193            None,
    194            contract.issuerCorpID if contract.forCorp else contract.issuerID,
    195            None,
    196            None,
    197            contract.type,
    198            reset))]
    199         m += [None]
    200         m += [(MenuLabel('UI/Contracts/ContractsWindow/FromSameSolarSystem'), self.FindRelated, (None,
    201            None,
    202            None,
    203            None,
    204            contract.startSolarSystemID,
    205            None,
    206            contract.type,
    207            reset))]
    208         m += [(MenuLabel('UI/Contracts/ContractsWindow/FromSameConstellation'), self.FindRelated, (None,
    209            None,
    210            None,
    211            None,
    212            startConstellationID,
    213            None,
    214            contract.type,
    215            reset))]
    216         m += [(MenuLabel('UI/Contracts/ContractsWindow/FromSameRegion'), self.FindRelated, (None,
    217            None,
    218            None,
    219            None,
    220            contract.startRegionID,
    221            None,
    222            contract.type,
    223            reset))]
    224         if contract.type == const.conTypeCourier:
    225             endConstellationID = sm.GetService('map').GetItem(contract.endSolarSystemID).locationID
    226             m += [None]
    227             m += [(MenuLabel('UI/Contracts/ContractsWindow/ToSameSolarSystem'), self.FindRelated, (None,
    228                None,
    229                None,
    230                None,
    231                None,
    232                contract.endSolarSystemID,
    233                contract.type,
    234                reset))]
    235             m += [(MenuLabel('UI/Contracts/ContractsWindow/ToSameConstellation'), self.FindRelated, (None,
    236                None,
    237                None,
    238                None,
    239                None,
    240                endConstellationID,
    241                contract.type,
    242                reset))]
    243             m += [(MenuLabel('UI/Contracts/ContractsWindow/ToSameRegion'), self.FindRelated, (None,
    244                None,
    245                None,
    246                None,
    247                None,
    248                contract.endRegionID,
    249                contract.type,
    250                reset))]
    251         return m
    252 
    253     def DispatchAssignedNotification(self, assignedToMe):
    254         if len(assignedToMe) > 0:
    255             firstContractID = assignedToMe[0]
    256             data = ContractAssignedFormatter.MakeData(contractCountAssignedToMe=len(assignedToMe), firstContractID=firstContractID)
    257             sm.GetService('notificationSvc').MakeAndScatterNotification(notificationConst.notificationTypeContractAssigned, data)
    258 
    259     def DispatchAttentionNotification(self, needsAttention):
    260         if len(needsAttention) > 0:
    261             firstContractInfo = needsAttention[0]
    262             data = ContractNeedsAttentionFormatter.MakeData(needsAttention=len(needsAttention), isForCorp=firstContractInfo[1], firstContractID=firstContractInfo.contractID)
    263             sm.GetService('notificationSvc').MakeAndScatterNotification(notificationConst.notificationTypeContractNeedsAttention, data)
    264 
    265     def DispatchAcceptedNotication(self, contractId):
    266         sm.GetService('notificationSvc').MakeAndScatterNotification(notificationConst.notificationTypeContractAccepted, {'contractId': contractId})
    267 
    268     def OnNotificationUiReady(self):
    269         uthread.new(self.NeocomBlink)
    270 
    271     def NeocomBlink(self):
    272         kv = self.GetContractProxySvc().GetLoginInfo()
    273         ignoreList = set(settings.user.ui.Get('contracts_ignorelist', []))
    274         assignedToMe = []
    275         for contract in kv.assignedToMe:
    276             if contract.issuerID not in ignoreList:
    277                 assignedToMe.append(contract.contractID)
    278 
    279         self.DispatchAssignedNotification(assignedToMe)
    280         self.DispatchAttentionNotification(kv.needsAttention)
    281         if len(kv.inProgress):
    282             ip = {}
    283             for contract in kv.inProgress:
    284                 ip[contract.contractID] = [contract.startStationID, contract.endStationID, contract.expires]
    285 
    286             self.contractsInProgress = ip
    287 
    288     def ProcessSessionChange(self, isremote, sess, change):
    289         if 'charid' in change and change['charid'][1] is not None:
    290             self.GetContractProxySvc()
    291             self.Init()
    292 
    293     def OnSessionReset(self):
    294         self.ReInitialize()
    295 
    296     def _NotifyOfEscrowChange(self):
    297         sm.ScatterEvent('OnContractEscrowChanged')
    298 
    299     def OnContractCreated(self):
    300         self._NotifyOfEscrowChange()
    301 
    302     def OnContractAssigned(self, contractID):
    303         contract = self.GetContract(contractID)
    304         c = contract.contract
    305         issuerID = [c.issuerID, c.issuerCorpID][c.forCorp]
    306         ignoreList = settings.user.ui.Get('contracts_ignorelist', [])
    307         if issuerID in ignoreList:
    308             return
    309         self.DispatchAssignedNotification([contractID])
    310         self.ContractNotify('UI/Contracts/ContractsService/ContractMessageAssigned', contractID)
    311 
    312     def OnContractCompleted(self, contractID):
    313         self.ContractNotify('UI/Contracts/ContractsService/ContractMessageCompleted', contractID)
    314         self._NotifyOfEscrowChange()
    315 
    316     def OnContractAccepted(self, contractID):
    317         self.DispatchAcceptedNotication(contractID)
    318         self._NotifyOfEscrowChange()
    319 
    320     def OnContractRejected(self, contractID):
    321         self.ContractNotify('UI/Contracts/ContractsService/ContractMessageRejected', contractID)
    322         self._NotifyOfEscrowChange()
    323 
    324     def OnContractFailed(self, contractID):
    325         self.ContractNotify('UI/Contracts/ContractsService/ContractMessageFailed', contractID)
    326         self._NotifyOfEscrowChange()
    327 
    328     def OnContractOutbid(self, contractID):
    329         self.ContractNotify('UI/Contracts/ContractsService/ContractMessageOutbid', contractID)
    330         self._NotifyOfEscrowChange()
    331 
    332     def OnContractBuyout(self, contractID):
    333         self.ContractNotify('UI/Contracts/ContractsService/ContractMessageBuyout', contractID)
    334         self._NotifyOfEscrowChange()
    335 
    336     def ContractNotify(self, cerberusLabel, contractID):
    337         MAX_NUM_MESSAGES = 5
    338         contract = self.GetContract(contractID, force=False)
    339         c = contract.contract
    340         title = GetContractTitle(c, contract.items)
    341         link = '<a href="contract:%d//%d">%s</a>' % (c.startSolarSystemID, contractID, title)
    342         message = localization.GetByLabel(cerberusLabel, timeStamp=FmtDate(blue.os.GetWallclockTime(), 'ls'), contractLink=link)
    343         self.messages.append(message)
    344         if len(self.messages) > MAX_NUM_MESSAGES:
    345             self.messages.pop(0)
    346         self.Blink()
    347         self.ClearCache()
    348 
    349     def Blink(self):
    350         sm.GetService('neocom').Blink('contracts', localization.GetByLabel('UI/Contracts/ContractsWindow/ContractsRequireAttention'))
    351 
    352     def ShowAssignedTo(self, *args):
    353         self.Show()
    354         self.OpenAvailableTab(3, 1)
    355 
    356     def Show(self, lookup = None, idx = None):
    357         ContractsWindow.CloseIfOpen()
    358         ContractsWindow.Open(lookup=lookup, idx=idx)
    359 
    360     def GetRouteSecurityWarning(self, startSolarSystemID, endSolarSystemID):
    361         securitySvc = sm.GetService('securitySvc')
    362         path = sm.GetService('clientPathfinderService').GetAutopilotPathBetween(startSolarSystemID, endSolarSystemID)
    363         txt = ''
    364         if not path:
    365             txt = '<color=0xffbb0000>%s</color>' % sm.GetService('clientPathfinderService').GetNoRouteFoundText(endSolarSystemID).upper()
    366         else:
    367             mySecurityClass = securitySvc.get_modified_security_class(session.solarsystemid2)
    368             pathClasses = set()
    369             for solarSystemID in path:
    370                 pathClasses.add(securitySvc.get_modified_security_class(solarSystemID))
    371 
    372             securityClassList = []
    373             for p in pathClasses:
    374                 if p != mySecurityClass:
    375                     securityClassList.append(GetSecurityClassText(p))
    376 
    377             securityClassString = localization.formatters.FormatGenericList(securityClassList)
    378             if len(securityClassList) > 0:
    379                 txt = localization.GetByLabel('UI/Contracts/ContractsService/RouteWillTakeYouThrough', listOfSecurityRatings=securityClassString)
    380         return txt
    381 
    382     def ShowContract(self, contractID):
    383         self.contractID = contractID
    384         ContractDetailsWindow.CloseIfOpen()
    385         ContractDetailsWindow.Open(contractID=contractID)
    386 
    387     def ShowManyContracts(self, contractIDs):
    388         MultiContractsItemExchangeWnd.CloseIfOpen()
    389         MultiContractsItemExchangeWnd.Open(contractIDs=contractIDs)
    390 
    391     def GetContract(self, contractID, force = False):
    392         v = self.contracts.get(contractID, None)
    393         diff = 0
    394         if v:
    395             diff = blue.os.GetWallclockTime() - v.time
    396         if contractID not in self.contracts or force or diff > 5 * const.MIN and not force:
    397             self.LogInfo('Fetching contract %s from the server' % contractID)
    398             contract = self.GetContractProxySvc().GetContract(contractID)
    399             v = utillib.KeyVal()
    400             v.time = blue.os.GetWallclockTime()
    401             v.contract = contract
    402             self.contracts[contractID] = v
    403         else:
    404             self.LogInfo('Using client cache for contract %s' % contractID)
    405         return self.contracts[contractID].contract
    406 
    407     def AcceptContract(self, contractID, forCorp):
    408         contract = self.GetContract(contractID, force=None)
    409         c = contract.contract
    410         if not contract:
    411             raise UserError('ConContractNotFound')
    412         if not self.CheckEndpointAccess(c.startStationID):
    413             return
    414         if c.endStationID and c.endStationID != c.startStationID and not self.CheckEndpointAccess(c.endStationID):
    415             return
    416         n = sm.GetService('clientPathfinderService').GetAutopilotJumpCount(session.solarsystemid2, c.startSolarSystemID)
    417         n2 = 0
    418         if c.endSolarSystemID:
    419             n2 = sm.GetService('clientPathfinderService').GetAutopilotJumpCount(session.solarsystemid2, c.endSolarSystemID)
    420         n = max(n, n2)
    421         if n > 1000:
    422             if prompt_player_fsd_dialog('ConConfirmNotReachable', {}, uiconst.YESNO) != uiconst.ID_YES:
    423                 return False
    424         wallet = sm.GetService('corp').GetMyCorpAccountName()
    425         args = {}
    426         if forCorp:
    427             args['youoryourcorp'] = localization.GetByLabel('UI/Contracts/ContractsService/YouOnBehalfOfYourCorp', corpName=cfg.eveowners.Get(session.corpid).name, wallet=wallet)
    428         else:
    429             args['youoryourcorp'] = localization.GetByLabel('UI/Common/You')
    430         args['contractname'] = GetContractTitle(c, contract.items)
    431         if c.type == const.conTypeAuction:
    432             raise RuntimeError('You cannot accept an auction')
    433         elif c.type == const.conTypeItemExchange:
    434             msg = 'ConConfirmAcceptItemExchange'
    435             reportGet = reportPay = ''
    436             for item in contract.items:
    437                 if item.inCrate:
    438                     reportGet += '<t>%s.<br>' % cfg.FormatConvert(const.UE_TYPEIDANDQUANTITY, item.itemTypeID, max(1, item.quantity))
    439                 else:
    440                     reportPay += '<t>%s.<br>' % cfg.FormatConvert(const.UE_TYPEIDANDQUANTITY, item.itemTypeID, max(1, item.quantity))
    441 
    442             if reportGet != '':
    443                 reportGet = localization.GetByLabel('UI/Contracts/ContractsService/ConfirmItemsGet', items=reportGet)
    444             if reportPay != '':
    445                 reportPay = localization.GetByLabel('UI/Contracts/ContractsService/ConfirmAcceptItemsPay', items=reportPay)
    446             if reportGet == '' and c.reward == 0 and (reportPay != '' or c.price > 0):
    447                 msg = 'ConConfirmAcceptItemExchangeGift'
    448             if reportPay != '' and forCorp:
    449                 reportPay += localization.GetByLabel('UI/Contracts/ContractsWindow/ConfirmAcceptCorpItems')
    450             args['itemsget'] = reportGet
    451             args['itemspay'] = reportPay
    452             payorgetmoney = ''
    453             if c.price > 0:
    454                 payorgetmoney = localization.GetByLabel('UI/Contracts/ContractsService/ConfirmAcceptPayMoney', numISK=FmtISKWithDescription(c.price))
    455             elif c.reward > 0:
    456                 payorgetmoney = localization.GetByLabel('UI/Contracts/ContractsService/ConfirmAcceptGetMoney', numISK=FmtISKWithDescription(c.reward))
    457             args['payorgetmoney'] = payorgetmoney
    458         elif c.type == const.conTypeCourier:
    459             if c.volume > const_conCourierWarningVolume:
    460                 if prompt_player_fsd_dialog('ConNeedFreighter', {'vol': c.volume}, uiconst.YESNO, suppress=uiconst.ID_YES) != uiconst.ID_YES:
    461                     return False
    462             if c.volume <= const_conCourierWarningVolume:
    463                 ship = sm.GetService('godma').GetItem(session.shipid)
    464                 if ship:
    465                     maxVolume = ship.GetCapacity().capacity
    466                     if maxVolume < c.volume:
    467                         if prompt_player_fsd_dialog('ConCourierDoesNotFit', {'shipCapacity': maxVolume,
    468                          'packageVolume': c.volume}, uiconst.YESNO, suppress=uiconst.ID_YES) != uiconst.ID_YES:
    469                             return False
    470             msg = 'ConConfirmAcceptCourier'
    471             args['numdays'] = c.numDays
    472             args['destination'] = c.endStationID
    473             args['volume'] = c.volume
    474             collateral = ''
    475             if c.collateral > 0:
    476                 collateral = localization.GetByLabel('UI/Contracts/ContractsService/YouWillHaveToProvideCollateralAmount', numISK=FmtISKWithDescription(c.collateral))
    477             args['collateral'] = collateral
    478         else:
    479             raise RuntimeError('Invalid contract type')
    480         if prompt_player_fsd_dialog(msg, args, uiconst.YESNO) != uiconst.ID_YES:
    481             return False
    482         self.LogInfo('Accepting contract %s' % contractID)
    483         try:
    484             contract = self.GetContractProxySvc().AcceptContract(contractID, forCorp)
    485         except UserError as e:
    486             raise
    487 
    488         if contract.type == const.conTypeCourier:
    489             self.contractsInProgress[contract.contractID] = [contract.startStationID, contract.endStationID, contract.dateAccepted + const.DAY * contract.numDays]
    490         self.ClearCache()
    491         return True
    492 
    493     def DeleteContract(self, contractID):
    494         contract = self.GetContract(contractID, force=None)
    495         if not contract:
    496             raise UserError('ConContractNotFound')
    497         c = contract.contract
    498         if c.dateExpired >= blue.os.GetWallclockTime() and c.status != const.conStatusRejected:
    499             args = {}
    500             args['contractname'] = GetContractTitle(c, contract.items)
    501             msg = 'ConConfirmDeleteContract'
    502             if prompt_player_fsd_dialog(msg, args, uiconst.YESNO) != uiconst.ID_YES:
    503                 return False
    504         self.LogInfo('Deleting contract', contractID)
    505         try:
    506             if self.GetContractProxySvc().DeleteContract(contractID):
    507                 sm.ScatterEvent('OnDeleteContracts', [contractID])
    508         except UserError as e:
    509             raise
    510 
    511         self.ClearCache()
    512         return True
    513 
    514     def DeleteMultipleContracts(self, contractIDs):
    515         if not contractIDs:
    516             return
    517         if prompt_player_fsd_dialog('ConfirmDeleteMultipleContracts', {'numContracts': len(contractIDs)}, uiconst.YESNO) != uiconst.ID_YES:
    518             return False
    519         try:
    520             deleted, failed = self.GetContractProxySvc().DeleteMultipleContracts(contractIDs)
    521             if deleted:
    522                 if failed:
    523                     text = localization.GetByLabel('UI/Contracts/ContractsService/MultipleContractsDeletedAndFailed', numDeleted=len(deleted), numFailed=len(failed))
    524                 else:
    525                     text = localization.GetByLabel('UI/Contracts/ContractsService/MultipleContractsDeleted', numDeleted=len(deleted))
    526             else:
    527                 text = localization.GetByLabel('UI/Contracts/ContractsService/MultipleContractsFailedToDelete', numFailed=len(failed))
    528             message_player(text)
    529             if deleted:
    530                 sm.ScatterEvent('OnDeleteContracts', deleted)
    531         except UserError as e:
    532             raise
    533 
    534         self.ClearCache()
    535         return True
    536 
    537     def CompleteContract(self, contractID):
    538         ret = False
    539         if prompt_player_fsd_dialog('ConConfirmComplete', None, uiconst.YESNO, suppress=uiconst.ID_YES) == uiconst.ID_YES:
    540             self.LogInfo('Completing contract %s' % contractID)
    541             self.ClearCache()
    542             try:
    543                 ret = self.GetContractProxySvc().CompleteContract(contractID, const.conStatusFinished)
    544             except UserError as e:
    545                 raise
    546 
    547             if contractID in self.contractsInProgress:
    548                 del self.contractsInProgress[contractID]
    549             prompt_player_fsd_dialog('ConCourierContractDelivered')
    550         return ret
    551 
    552     def FailContract(self, contractID):
    553         contract = self.GetContract(contractID, force=None)
    554         c = contract.contract
    555         args = {}
    556         if not contract:
    557             raise UserError('ConContractNotFound')
    558         args['contractname'] = GetContractTitle(c, contract.items)
    559         if c.acceptorID == session.charid or c.acceptorID == session.corpid:
    560             msg = 'ConConfirmFailContractAcceptor'
    561             args['collateral'] = localization.GetByLabel('UI/Contracts/ContractsService/ConfirmFailLoseCollateral', numISK=FmtISKWithDescription(c.collateral))
    562         else:
    563             dateOverdue = c.dateAccepted + const.DAY * c.numDays
    564             isOverdue = blue.os.GetWallclockTime() > dateOverdue
    565             if isOverdue and c.status == const.conStatusInProgress:
    566                 msg = 'ConConfirmFailContractIssuerOverdue'
    567                 args['timeoverdue'] = FmtDate(blue.os.GetWallclockTime() - dateOverdue, 'ls')
    568             else:
    569                 msg = 'ConConfirmFailContractIssuerFinishedAcceptor'
    570             args['acceptor'] = cfg.eveowners.Get(c.acceptorID).name
    571         if prompt_player_fsd_dialog(msg, args, uiconst.YESNO) != uiconst.ID_YES:
    572             return False
    573         self.ClearCache()
    574         self.LogInfo('Failing contract %s' % contractID)
    575         try:
    576             ret = self.GetContractProxySvc().CompleteContract(contractID, const.conStatusFailed)
    577         except UserError as e:
    578             raise
    579 
    580         if contractID in self.contractsInProgress:
    581             del self.contractsInProgress[contractID]
    582         return ret
    583 
    584     def RejectContract(self, contractID):
    585         contract = self.GetContract(contractID, force=None)
    586         c = contract.contract
    587         args = {}
    588         if not contract:
    589             raise UserError('ConContractNotFound')
    590         args['contractname'] = GetContractTitle(c, contract.items)
    591         args['issuer'] = cfg.eveowners.Get({False: c.issuerID,
    592          True: c.issuerCorpID}[c.forCorp]).name
    593         if c.assigneeID == session.charid:
    594             msg = 'ConConfirmRejectContract'
    595         elif c.assigneeID == session.corpid:
    596             msg = 'ConConfirmRejectContractCorp'
    597         elif session.allianceid and c.assigneeID == session.allianceid:
    598             msg = 'ConConfirmRejectContractAlliance'
    599         else:
    600             raise UserError('ConNotYourContract')
    601         if prompt_player_fsd_dialog(msg, args, uiconst.YESNO, suppress=uiconst.ID_YES) != uiconst.ID_YES:
    602             return False
    603         self.LogInfo('Rejecting contract %s' % contractID)
    604         self.ClearCache()
    605         try:
    606             return self.GetContractProxySvc().CompleteContract(contractID, const.conStatusRejected)
    607         except UserError as e:
    608             raise
    609 
    610     def CreateContract(self, contractInfo, confirm = None):
    611         self.LogNotice('CreateContract, contractInfo = ', contractInfo)
    612         try:
    613             ret = self.GetContractProxySvc().CreateContract(contractInfo, confirm=confirm)
    614         except UserError as e:
    615             raise
    616 
    617         self.ClearCache()
    618         return ret
    619 
    620     def PlaceBid(self, contractID, force = None):
    621         uthread.ReentrantLock(self)
    622         try:
    623             isContractMgr = session.corprole & const.corpRoleContractManager == const.corpRoleContractManager
    624             contract = self.GetContract(contractID, force=force)
    625             self.contract = contract
    626             c = contract.contract
    627             if not self.CheckEndpointAccess(c.startStationID):
    628                 return
    629             currentBid = 0
    630             numBids = 0
    631             maxBid = MAX_AMOUNT
    632             if len(contract.bids) > 0:
    633                 currentBid = contract.bids[0].amount
    634                 numBids = len(contract.bids)
    635             b = currentBid + max(int(0.1 * c.price), 1000)
    636             if c.collateral > 0:
    637                 b = min(b, c.collateral)
    638                 maxBid = c.collateral
    639             minBid = int(max(c.price, b))
    640             if c.collateral > 0:
    641                 collateral = FmtISKWithDescription(c.collateral)
    642             else:
    643                 collateral = localization.GetByLabel('UI/Contracts/ContractEntry/NoBuyoutPrice')
    644             biddingOnLabel = localization.GetByLabel('UI/Contracts/ContractsService/BiddingOnName', contractName=GetContractTitle(c, contract.items))
    645             startingBidLabel = localization.GetByLabel('UI/Contracts/ContractsWindow/StartingBid')
    646             buyoutLabel = localization.GetByLabel('UI/Contracts/ContractsWindow/BuyoutPrice')
    647             currentLabel = localization.GetByLabel('UI/Contracts/ContractsWindow/CurrentBid')
    648             yourBidLabel = localization.GetByLabel('UI/Contracts/ContractsWindow/YourBid')
    649             format = [{'type': 'text',
    650               'text': biddingOnLabel,
    651               'labelwidth': 100},
    652              {'type': 'header',
    653               'text': startingBidLabel},
    654              {'type': 'text',
    655               'text': FmtISKWithDescription(c.price)},
    656              {'type': 'header',
    657               'text': buyoutLabel},
    658              {'type': 'text',
    659               'text': collateral},
    660              {'type': 'header',
    661               'text': currentLabel},
    662              {'type': 'text',
    663               'text': FmtISKWithDescription(currentBid)},
    664              {'type': 'edit',
    665               'setvalue': '0.1',
    666               'floatonly': [0, maxBid],
    667               'setvalue': minBid,
    668               'key': 'bid',
    669               'labelwidth': 100,
    670               'label': yourBidLabel,
    671               'required': 1,
    672               'setfocus': 1}]
    673             if isContractMgr and not (c.forCorp and c.issuerCorpID == session.corpid):
    674                 format.append({'type': 'checkbox',
    675                  'required': 1,
    676                  'height': 16,
    677                  'setvalue': 0,
    678                  'key': 'forCorp',
    679                  'label': '',
    680                  'text': localization.GetByLabel('UI/Contracts/ContractsWindow/PlaceBidForCorp')})
    681             if c.collateral > 0:
    682                 format.append({'type': 'checkbox',
    683                  'required': 1,
    684                  'height': 16,
    685                  'setvalue': 0,
    686                  'key': 'buyout',
    687                  'label': '',
    688                  'text': localization.GetByLabel('UI/Contracts/ContractsWindow/Buyout'),
    689                  'onchange': self.PlaceBidBuyoutCallback})
    690             format.append({'type': 'push'})
    691             retval = uix.HybridWnd(format, localization.GetByLabel('UI/Contracts/ContractsWindow/BiddingOnContract'), 'biddingOnContract', modal=1, buttons=uiconst.OKCANCEL, minW=340, minH=100, icon='res:/ui/Texture/WindowIcons/wallet.png')
    692             if retval:
    693                 forCorp = not not retval.get('forCorp', False)
    694                 buyout = not not retval.get('buyout', False)
    695                 bid = int(retval['bid'])
    696                 if buyout:
    697                     if c.collateral < c.price:
    698                         raise RuntimeError('Buyout is lower than starting bid!')
    699                     bid = c.collateral
    700                 try:
    701                     retval = self.DoPlaceBid(contractID, bid, forCorp)
    702                 except UserError as e:
    703                     if e.args[0] == 'ConBidTooLow':
    704                         prompt_player_fsd_dialog(e.args[0], e.args[1])
    705                         self.PlaceBid(contractID, force=True)
    706                     else:
    707                         raise
    708 
    709             return not not retval
    710         finally:
    711             uthread.UnLock(self)
    712             self.ClearCache()
    713 
    714     def PlaceBidBuyoutCallback(self, chkbox, *args):
    715         c = self.contract.contract
    716         buyout = c.collateral
    717         currentBid = 0
    718         if len(self.contract.bids) > 0:
    719             currentBid = self.contract.bids[0].amount
    720         minBid = int(max(c.price, min(currentBid + 0.1 * c.price + 0.1, c.collateral)))
    721         wnd = chkbox.parent.parent
    722         edit = wnd.FindChild('edit_bid')
    723         if chkbox.GetValue():
    724             edit.state = uiconst.UI_DISABLED
    725             edit.SetText(buyout)
    726         else:
    727             edit.state = uiconst.UI_NORMAL
    728             edit.SetText(minBid)
    729 
    730     def DoPlaceBid(self, contractID, bid, forCorp):
    731         contract = self.GetContract(contractID, force=None)
    732         c = contract.contract
    733         args = {}
    734         if not contract:
    735             raise UserError('ConContractNotFound')
    736         wallet = sm.GetService('corp').GetMyCorpAccountName()
    737         args = {}
    738         if forCorp:
    739             args['youoryourcorp'] = localization.GetByLabel('UI/Contracts/ContractsService/YouOnBehalfOfYourCorp', corpName=cfg.eveowners.Get(session.corpid).name, wallet=wallet)
    740         else:
    741             args['youoryourcorp'] = localization.GetByLabel('UI/Common/You')
    742         args['contractname'] = GetContractTitle(c, contract.items)
    743         args['amount'] = FmtISKWithDescription(bid)
    744         reportGet = ''
    745         for item in contract.items:
    746             if item.inCrate:
    747                 reportGet += '<t>%s.<br>' % cfg.FormatConvert(const.UE_TYPEIDANDQUANTITY, item.itemTypeID, max(1, item.quantity))
    748 
    749         if reportGet != '':
    750             reportGet = localization.GetByLabel('UI/Contracts/ContractsService/ConfirmItemsGet', items=reportGet)
    751         args['itemsget'] = reportGet
    752         msg = 'ConConfirmPlaceBid'
    753         if prompt_player_fsd_dialog(msg, args, uiconst.YESNO, suppress=uiconst.ID_YES) != uiconst.ID_YES:
    754             return False
    755         self.LogInfo('Placing bid of %s on contract %s' % (FmtISKWithDescription(bid), contractID))
    756         try:
    757             return self.GetContractProxySvc().PlaceBid(contractID, bid, forCorp)
    758         except UserError as e:
    759             raise
    760 
    761     def FinishAuction(self, contractID, isIssuer):
    762         self.LogInfo('Finishing Auction %s' % contractID)
    763         self.ClearCache()
    764         try:
    765             return self.GetContractProxySvc().FinishAuction(contractID, isIssuer)
    766         except UserError as e:
    767             raise
    768 
    769     def GetMarketTypes(self):
    770         if not self.markettypes:
    771             self.markettypes = GetMarketTypes()
    772         return self.markettypes
    773 
    774     def NumOutstandingContracts(self, forCorp = False):
    775         self.LogInfo('Getting number of outstanding contracts%s' % ['', ' (for corp)'][forCorp])
    776         return self.GetContractProxySvc().NumOutstandingContracts()
    777 
    778     def SplitStack(self, stationID, itemID, qty, forCorp, flag):
    779         self.LogInfo('Splitting stack of item %s, qty=%s%s' % (itemID, qty, ['', ' (for corp)'][forCorp]))
    780         ret = self.GetContractProxySvc().SplitStack(stationID, itemID, qty, forCorp, flag)
    781         if ret:
    782             wnd = CreateContractWnd.GetIfOpen()
    783             if wnd is not None and not wnd.destroyed:
    784                 wnd.OnItemSplit(itemID, qty)
    785                 wnd.Refresh()
    786         return ret
    787 
    788     def GetSolarSystemIDForStationOrStructure(self, stationID):
    789         if idCheckers.IsStation(stationID):
    790             return sm.GetService('ui').GetStationStaticInfo(stationID).solarSystemID
    791         else:
    792             structureInfo = sm.GetService('structureDirectory').GetStructureInfo(stationID)
    793             if structureInfo is not None:
    794                 return structureInfo.solarSystemID
    795             return
    796 
    797     def GetItemsInContainer(self, stationID, containerID, forCorp, flag):
    798         return sorted(self.GetContractProxySvc().GetItemsInContainer(stationID, containerID, forCorp, flag) or [], key=lambda i: evetypes.GetName(i.typeID))
    799 
    800     def GetNumItemsInContainers(self, stationID, containerIDs, forCorp, flag):
    801         return self.GetContractProxySvc().GetNumItemsInContainers(stationID, containerIDs, forCorp, flag)
    802 
    803     def GetItemsInDockableLocation(self, stationID, forCorp):
    804         return self.GetContractProxySvc().GetItemsInDockableLocation(stationID, forCorp)
    805 
    806     def DeleteNotification(self, contractID, forCorp):
    807         self.ClearCache()
    808         return self.GetContractProxySvc().DeleteNotification(contractID, forCorp)
    809 
    810     def CollectMyPageInfo(self, force = False):
    811         skillsSvc = sm.GetService('skills')
    812         if self.myPageInfo is None or self.myPageInfo.timeout < blue.os.GetWallclockTime() or force:
    813             mpi = self.GetContractProxySvc().CollectMyPageInfo()
    814             contractingLevel = skillsSvc.GetEffectiveLevel(typeContracting) or 0
    815             advancedContractingLevel = skillsSvc.GetEffectiveLevel(typeAdvancedContracting) or 0
    816             corporationContractingLevel = skillsSvc.GetEffectiveLevel(typeCorporationContracting) or 0
    817             maxNumContracts = NUM_CONTRACTS_BY_SKILL[contractingLevel] + NUM_CONTRACTS_BY_ADVSKILL[advancedContractingLevel]
    818             maxNumContractsCorp = NUM_CONTRACTS_BY_SKILL_CORP[corporationContractingLevel]
    819             mpi.numContractsLeft = max(maxNumContracts - mpi.numOutstandingContractsNonCorp, 0)
    820             mpi.numContractsLeftForCorp = max(maxNumContractsCorp - mpi.numOutstandingContractsForCorp, 0)
    821             mpi.numContractsTotal = maxNumContracts
    822             mpi.numContractsLeftInCorp = max(MAX_NUM_CONTRACTS - mpi.numOutstandingContracts, 0)
    823             mpi.timeout = blue.os.GetWallclockTime() + CACHE_TIME * MINUTE
    824             self.myPageInfo = mpi
    825         else:
    826             self.LogInfo('CollectMyPageInfo returning cached result')
    827             mpi = self.myPageInfo
    828         return mpi
    829 
    830     def GetMyExpiredContractList(self):
    831         if self.myExpiredContractList is None or self.myExpiredContractList.timeout < blue.os.GetWallclockTime():
    832             l = utillib.KeyVal(mySelf=None, myCorp=None, expires=0)
    833             l.mySelf = sm.ProxySvc('contractProxy').GetMyExpiredContractList(False)
    834             l.myCorp = sm.ProxySvc('contractProxy').GetMyExpiredContractList(True)
    835             l.timeout = blue.os.GetWallclockTime() + CACHE_TIME * MINUTE
    836             self.myExpiredContractList = l
    837         else:
    838             self.LogInfo('GetMyExpiredContractList returning cached result')
    839             l = self.myExpiredContractList
    840         return l
    841 
    842     def GetMyBids(self, forCorp):
    843         l = None
    844         val = getattr(self, 'myBids_%s' % forCorp, None)
    845         if val is None or val.timeout < blue.os.GetWallclockTime():
    846             l = utillib.KeyVal(mySelf=None, myCorp=None, expires=0)
    847             l.list = sm.ProxySvc('contractProxy').GetMyBids(forCorp)
    848             l.timeout = blue.os.GetWallclockTime() + CACHE_TIME * MINUTE
    849             setattr(self, 'myBids_%s' % forCorp, l)
    850         else:
    851             self.LogInfo('GetMyBids(', forCorp, ') returning cached result')
    852             l = val
    853         return l
    854 
    855     def GetMyContractEscrow(self):
    856         iskEscrow = 0.0
    857         itemsEscrow = 0.0
    858         try:
    859             contractEscrow = self.GetContractProxySvc().GetMyContractEscrow()
    860             iskEscrow = float(contractEscrow.iskEscrow or 0)
    861             itemsEscrow = float(contractEscrow.itemsEscrow or 0)
    862         except Exception as exception:
    863             msg = 'Failed to calculate total contract escrow, cause: %s' % getattr(exception, 'msg', str(exception))
    864             self.LogException(msg)
    865 
    866         return (iskEscrow, itemsEscrow)
    867 
    868     def GetMyCurrentContractList(self, isAccepted, forCorp):
    869         l = None
    870         val = getattr(self, 'myCurrentList_%s_%s' % (isAccepted, forCorp), None)
    871         if val is None or val.timeout < blue.os.GetWallclockTime():
    872             l = utillib.KeyVal(mySelf=None, myCorp=None, expires=0)
    873             l.list = sm.ProxySvc('contractProxy').GetMyCurrentContractList(isAccepted, forCorp)
    874             l.timeout = blue.os.GetWallclockTime() + CACHE_TIME * MINUTE
    875             setattr(self, 'myCurrentList_%s_%s' % (isAccepted, forCorp), l)
    876         else:
    877             self.LogInfo('GetMyCurrentContractList(', isAccepted, forCorp, ') returning cached result')
    878             l = val
    879         return l
    880 
    881     def ClearCache(self):
    882         setattr(self, 'myCurrentList_True_True', None)
    883         setattr(self, 'myCurrentList_True_False', None)
    884         setattr(self, 'myCurrentList_False_True', None)
    885         setattr(self, 'myCurrentList_False_False', None)
    886         setattr(self, 'myBids_False', None)
    887         setattr(self, 'myBids_True', None)
    888         self.myExpiredContractList = None
    889         self.myPageInfo = None
    890         sm.ScatterEvent('OnContractCacheCleared')
    891         self._NotifyOfEscrowChange()
    892 
    893     def ShowContracts(self, status = 0, forCorp = 0):
    894         wnd = ContractsWindow.GetIfOpen()
    895         if not wnd:
    896             wnd = ContractsWindow.ToggleOpenClose()
    897         wnd.ShowMyContractsPanel(status, forCorp)
    898 
    899     def OpenCreateContract(self, items = None, contract = None, copyContractDisvision = None):
    900         CreateContractWnd.Open(recipientID=None, contractItems=items, copyContract=contract, copyContractDisvision=copyContractDisvision)
    901 
    902     def OpenCreateContractFromIGB(self, contractType = None, stationID = None, itemIDs = None):
    903         CreateContractWnd.CloseIfOpen()
    904         CreateContractWnd.Open(recipientID=None, contractItems=None, copyContract=None, contractType=contractType, stationID=stationID, itemIDs=itemIDs)
    905 
    906     def OpenAvailableTab(self, view, reset = False, typeID = None, contractType = CONTYPE_AUCTIONANDITEMECHANGE):
    907         wnd = ContractsWindow.GetIfOpen()
    908         if wnd is not None:
    909             assigneeID = {3: const.conAvailMyself,
    910              4: const.conAvailMyCorp}.get(view, const.conAvailPublic)
    911             self.OpenSearchTab(typeID, None, None, None, None, None, assigneeID, contractType)
    912 
    913     def OpenSearchTab(self, *args):
    914         wnd = ContractsWindow.Open()
    915         blue.pyos.synchro.SleepWallclock(10)
    916         wnd.tabGroup.SelectByIdx(2)
    917         wnd.contractSearchContent.FindRelated(*args)
    918 
    919     def AddIgnore(self, issuerID):
    920         ignoreList = settings.user.ui.Get('contracts_ignorelist', [])
    921         if len(ignoreList) >= MAX_IGNORED:
    922             raise UserError('ConIgnoreListFull', {'n': MAX_IGNORED})
    923         if issuerID not in ignoreList:
    924             ignoreList.append(issuerID)
    925         settings.user.ui.Set('contracts_ignorelist', ignoreList)
    926         self.ClearCache()
    927         sm.ScatterEvent('OnAddIgnore', issuerID)
    928 
    929     def ForgetAuctionContract(self, contractID, forCorp):
    930         self.GetContractProxySvc().DeleteContractNotification(contractID, forCorp)
    931         self.myExpiredContractList = None
    932 
    933     def OpenIgnoreList(self):
    934         IgnoreListWindow.CloseIfOpen()
    935         IgnoreListWindow.Open()
    936 
    937     def GetMessages(self):
    938         return self.messages
    939 
    940     def DeliverCourierContractFromItemID(self, itemID):
    941         info = self.GetBasicContractInfoFromItemID(itemID)
    942         if info is None:
    943             raise UserError('ConContractNotFoundForCrate')
    944         contractID = info.contractID
    945         self.CompleteContract(contractID)
    946 
    947     def FindCourierContractFromItemID(self, itemID):
    948         info = self.GetBasicContractInfoFromItemID(itemID)
    949         if info is None:
    950             raise UserError('ConContractNotFoundForCrate')
    951         self.ShowContract(info.contractID)
    952 
    953     def GetBasicContractInfoFromItemID(self, itemID):
    954         return self.GetContractProxySvc().GetCourierContractFromItemID(itemID)
    955 
    956     def GetEndStationIDForCourierWrapID(self, itemID):
    957         contractInfo = self.GetBasicContractInfoFromItemID(itemID)
    958         if contractInfo is None:
    959             return
    960         contract = self.GetContract(contractInfo.contractID, force=True)
    961         c = contract.contract
    962         return c.endStationID
    963 
    964     @Memoize(1)
    965     def IsStationInaccessible(self, stationID):
    966         if stationID is None:
    967             return False
    968         if idCheckers.IsStation(stationID):
    969             return False
    970         if stationID == session.structureid:
    971             return False
    972         try:
    973             if sm.GetService('structureSettings').CharacterHasService(stationID, structures.SERVICE_DOCKING):
    974                 return False
    975             return True
    976         except ValueError:
    977             return True
    978 
    979         return False
    980 
    981     def CheckEndpointAccess(self, endpointID):
    982         if idCheckers.IsStation(endpointID):
    983             return bool(sm.GetService('ui').GetStation(endpointID))
    984         structure = sm.GetService('structureDirectory').GetStructureInfo(endpointID)
    985         if structure is not None:
    986             if session.structureid == endpointID:
    987                 return True
    988             linkInfo = ('showinfo', structure.typeID)
    989             header = localization.GetByLabel('UI/Common/Confirm')
    990             question = localization.GetByLabel('UI/Contracts/ContractsService/StructureMayBeInaccessible', structureID=endpointID, typeID=structure.typeID, linkinfo=linkInfo)
    991             if prompt_player_fsd_dialog('CustomQuestion', {'header': header,
    992              'question': question}, uiconst.YESNO) != uiconst.ID_YES:
    993                 return False
    994             return True
    995         raise RuntimeError("Endpoint doesn't resolve to a station or player owned structure")
    996 
    997     def GetSystemSecurityDot(self, solarSystemID):
    998         security_status = get_security_status(solarSystemID)
    999         color = get_security_status_color(security_status)
   1000         return eveformat.color(u'\u2022', color=color)
   1001 
   1002     def OnShowContract(self, contractID):
   1003         self.ShowContract(contractID)
   1004 
   1005     def GetContractTypeIDFinder(self):
   1006         if self.contractTypeIDFinder is None:
   1007             with TempLock('GetContractTypeIDFinder'):
   1008                 if self.contractTypeIDFinder is None:
   1009                     typeIDs = [ x.typeID for x in self.GetMarketTypes() if CanRequestType(x.typeID) ]
   1010                     from textImporting.textToTypeIDFinder import TextToTypeIDFinder
   1011                     from textImporting import IsUsingDefaultLanguage
   1012                     usingDefaultLanguage = IsUsingDefaultLanguage(session)
   1013                     self.contractTypeIDFinder = TextToTypeIDFinder(typeIDs, usingDefaultLanguage)
   1014         return self.contractTypeIDFinder