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

uipointersvc.py (42858B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\services\uipointersvc.py
      2 import blue
      3 import collections
      4 import uthread
      5 import sys
      6 import log
      7 import carbonui.const as uiconst
      8 import telemetry
      9 import mathext
     10 import utillib
     11 from carbon.common.script.sys.service import Service
     12 from carbon.common.script.util.timerstuff import AutoTimer
     13 from carbonui.control.window import Window
     14 from carbonui.signals.ui_element_signals import on_unique_ui_element_created, on_unique_ui_element_destroyed
     15 from eve.client.script.ui.shared.neocom.neocom import neocomSettings
     16 from localization import GetByLabel
     17 from uihighlighting.const import UiHighlightDirections
     18 from uihighlighting.ui.uiutil import UI_CLASS_BY_NAME, DEFAULT_UI_CLASS
     19 from uihighlighting.ui.spaceobjectpointer import SpaceObjectPointer, SpaceObjectUiPointerData
     20 from uihighlighting.ui.uipointer import CustomUiPointer, UiPointerData
     21 from uihighlighting.ui.uipointer import UIPOINTER_WIDTH, UIPOINTER_HEIGHT, POINTER_ARROW_WIDTH, POINTER_ARROW_HEIGHT
     22 from uihighlighting import uihighlightaudio
     23 from uihighlighting.statelogger import StateLogger
     24 from carbonui.uicore import uicore
     25 import eve.client.script.ui.shared.pointerTool.pointerToolConst as pConst
     26 WAITING_FOR_ELEMENT_TO_COME_BACK_MS = 500
     27 HINT_WORKER_DELAY_MS = 2000
     28 HINT_DISPLAY_DELAY_MS = 250
     29 
     30 class UIPointerSvc(Service):
     31     __exportedcalls__ = {}
     32     __guid__ = 'svc.uipointerSvc'
     33     __notifyevents__ = ['OnSessionChanged',
     34      'ProcessSessionReset',
     35      'OnEveMenuShown',
     36      'DoBallRemove',
     37      'OnUIScalingChange',
     38      'OnToggleWindowed',
     39      'DoBallsRemove',
     40      'OnInvItemDropEnded']
     41     __servicename__ = 'UIPointers'
     42     __displayname__ = 'UI Pointer Service'
     43     __dependencies__ = ['michelle']
     44     specialCasedPointers = (pConst.UNIQUE_NAME_LOCAL_CHAT,
     45      pConst.UNIQUE_NAME_CORP_CHAT,
     46      pConst.UNIQUE_NAME_FLEET_CHAT,
     47      pConst.UNIQUE_NAME_NEXT_IN_ROUTE)
     48 
     49     def __init__(self):
     50         Service.__init__(self)
     51         self.running = True
     52         self.pointersAreSuppressed = False
     53         self.oldObscurers = {}
     54         self.statelogger = StateLogger(self)
     55 
     56     def Run(self, memStream = None):
     57         self.elementUrls = collections.defaultdict(list)
     58         self.pointerUIByItemID = {}
     59         self.spaceObjectPointerLock = uthread.Semaphore()
     60         self.pointerDataByItemID = {}
     61         self.pointerDataByDungeonObjectID = {}
     62         self.pointerDataByDungeonEntityGroupID = {}
     63         self.pointerDataByTypeID = {}
     64         self.pointerDataByByGroupID = {}
     65         self.pointerDataNextInRoute = None
     66         self.suppressedSpaceObjectUiPointers = set()
     67         self.spaceObjectUiPointerUpdater = None
     68         self.uiPointersByPointToID = {}
     69         self.uiElementsToHighlight = set()
     70         self.neocomSvc = sm.GetService('neocom')
     71         self.uiPointerUpdateThread = None
     72         on_unique_ui_element_created.connect(self._OnUniqueUiElementCreated)
     73         on_unique_ui_element_destroyed.connect(self._OnUniqueUiElementDestroyed)
     74 
     75     def Stop(self, memStream = None):
     76         self.running = False
     77         self.ClearPointers()
     78         on_unique_ui_element_created.disconnect(self._OnUniqueUiElementCreated)
     79         on_unique_ui_element_destroyed.disconnect(self._OnUniqueUiElementDestroyed)
     80 
     81     def _OnUniqueUiElementCreated(self, uniqueName, element):
     82         if uniqueName not in self.uiElementsToHighlight:
     83             return
     84         pointer = self.uiPointersByPointToID.get(uniqueName)
     85         if pointer and (pointer.pointToElement is None or pointer.pointToElement.destroyed):
     86             pointer.pointToElement = element
     87             self.ShowPointer(uniqueName)
     88 
     89     def _OnUniqueUiElementDestroyed(self, uniqueName):
     90         if uniqueName in self.uiPointersByPointToID:
     91             self.HidePointer(uniqueName)
     92 
     93     def ProcessSessionReset(self):
     94         self.ClearPointers()
     95 
     96     def OnSessionChanged(self, isremote, sess, change):
     97         if 'charid' in change and change['charid'][1] is not None:
     98             self.Run()
     99         if 'shipid' in change:
    100             oldShipID, newShipID = change['shipid']
    101             if newShipID is not None:
    102                 self.FlushSpaceObjectPointer(newShipID)
    103 
    104     def OnEveMenuShown(self):
    105         for pointer in self.uiPointersByPointToID.values():
    106             if pointer.pointToElement.name == 'eveMenuBtn':
    107                 self.ClearPointers()
    108 
    109     def OnUIScalingChange(self, change, *args):
    110         for pointToID in self.uiElementsToHighlight.copy():
    111             self.RestartUiPointer(pointToID)
    112 
    113         if self.spaceObjectUiPointerUpdater is not None:
    114             self.RestartSpacePointers(clearPointers=True)
    115 
    116     def OnToggleWindowed(self, isWindowed):
    117         if not isWindowed:
    118             self.RestartSpacePointers(clearPointers=True)
    119 
    120     def ShouldHidePointers(self):
    121         if uicore.layer.systemmenu.isopen:
    122             return True
    123         if sm.GetService('viewState').IsViewActive('charactercreation'):
    124             return True
    125         return self.pointersAreSuppressed
    126 
    127     def SuppressPointers(self):
    128         self.pointersAreSuppressed = True
    129 
    130     def RevealPointers(self):
    131         self.pointersAreSuppressed = False
    132 
    133     def UpdatePointers(self):
    134         if not self.uiElementsToHighlight:
    135             self.uiPointerUpdateThread = None
    136         for pointToID in self.uiElementsToHighlight.copy():
    137             try:
    138                 self._UpdatePointer(pointToID)
    139             except:
    140                 log.LogException("UpdatePointers failed for pointToID '%s'." % str(pointToID))
    141 
    142     def _UpdatePointer(self, pointToID):
    143         currentPointer = self.uiPointersByPointToID.get(pointToID, None)
    144         if currentPointer is None:
    145             return
    146         if self.ShouldHidePointers():
    147             self.HidePointer(pointToID)
    148         else:
    149             uiPointerElement = currentPointer.uiPointerElement
    150             pointToElement = currentPointer.pointToElement
    151             considerations = currentPointer.considerations
    152             oldPointLeft = currentPointer.oldPointLeft
    153             oldPointUp = currentPointer.oldPointUp
    154             oldPointDown = currentPointer.oldPointDown
    155             basicData = currentPointer.basicData
    156             if pointToElement is None or pointToElement.destroyed or isinstance(pointToElement, Window) and pointToElement.stacked:
    157                 blue.pyos.synchro.SleepWallclock(WAITING_FOR_ELEMENT_TO_COME_BACK_MS)
    158                 elementUrl = self.elementUrls.get(pointToID, None)
    159                 rediscoveredKeyVal = self.FindElementToPointTo(elementUrl)
    160                 rediscoveredElement = rediscoveredKeyVal.pointToElement if rediscoveredKeyVal else None
    161                 if rediscoveredElement is None or rediscoveredElement.destroyed:
    162                     self.HidePointer(pointToID)
    163                     return
    164                 pointToElement = rediscoveredElement
    165                 currentPointer.pointToElement = rediscoveredElement
    166                 uiPointerElement.UpdatePointToElement(rediscoveredElement)
    167             locationData = self.GetLocation(basicData, pointToElement, considerations)
    168             cumTop, cumLeft, pointLeft, pointUp, pointDown, isObscured, arrowPos = locationData
    169             if cumTop <= 0 and cumLeft <= 0 or pointToElement.state == uiconst.UI_HIDDEN:
    170                 self.HidePointer(pointToID)
    171             else:
    172                 self.ShowPointer(pointToID)
    173             if uiPointerElement.destroyed or pointLeft != oldPointLeft or pointUp != oldPointUp or pointDown != oldPointDown:
    174                 self.ClearPointer(pointToID)
    175                 basicData.audioSetting = False
    176                 elementContainer = self.ConstructPointer(pointLeft, pointUp, pointDown, arrowPos, basicData, pointToElement)
    177                 elementContainer.SetPosition(cumLeft, cumTop)
    178                 self.uiPointersByPointToID[pointToID] = UiPointerData(uiPointerElement=elementContainer, pointToElement=pointToElement, considerations=considerations, oldPointLeft=pointLeft, oldPointUp=pointUp, oldPointDown=pointDown, basicData=basicData)
    179                 uthread.new(self._UpdatePointer, pointToID)
    180             else:
    181                 uiPointerElement.SetPosition(cumLeft, cumTop)
    182 
    183     def FindDeep(self, element, idOfItemToFind, shouldExcludeInvisible = False):
    184         if getattr(element, 'uniqueUiName', None) == idOfItemToFind or getattr(element, 'name', None) == idOfItemToFind:
    185             if not shouldExcludeInvisible or getattr(element, 'display', False):
    186                 return element
    187         children = getattr(element, 'children', None)
    188         if not children:
    189             return
    190         for child in children:
    191             results = self.FindDeep(child, idOfItemToFind, shouldExcludeInvisible)
    192             if results is not None:
    193                 return results
    194 
    195     def GetLocation(self, basicData, element, directive):
    196         pointToID = basicData.pointToID
    197         defaultDirection = basicData.defaultDirection
    198         offset = basicData.offset or 0
    199         try:
    200             parent = element
    201             while hasattr(parent, 'parent'):
    202                 if parent is None or parent.state == uiconst.UI_HIDDEN:
    203                     return (-999,
    204                      -999,
    205                      False,
    206                      False,
    207                      False,
    208                      False,
    209                      0)
    210                 if self.SkipElementDueToClipping(element, parent) and not basicData.showEvenOffscreen:
    211                     return (-999,
    212                      -999,
    213                      False,
    214                      False,
    215                      False,
    216                      False,
    217                      0)
    218                 parent = parent.parent
    219 
    220             if pointToID in self.uiPointersByPointToID:
    221                 uiPointer = self.uiPointersByPointToID[pointToID]
    222                 pointerWidth = uiPointer.uiPointerElement.width
    223                 pointerHeight = uiPointer.uiPointerElement.height
    224             else:
    225                 uiPointer = None
    226                 pointerWidth, pointerHeight = UIPOINTER_WIDTH, UIPOINTER_HEIGHT
    227             if directive == 'shipui' and element.parent.name == 'slotsContainer':
    228                 if element.parent.name == 'slotsContainer':
    229                     slotsContainer = element.parent
    230                     cumTop, cumLeft = slotsContainer.absoluteTop + element.top, slotsContainer.absoluteLeft + element.left + 5
    231             elif directive == 'bracket':
    232                 parent = element.parent
    233                 cumTop, cumLeft = parent.absoluteTop + element.top, parent.absoluteLeft + element.left - 2
    234             elif directive == 'neocom':
    235                 cumTop, cumLeft = element.absoluteTop, element.absoluteLeft
    236             elif hasattr(element, 'absoluteTop') and hasattr(element, 'absoluteLeft'):
    237                 cumTop, cumLeft = element.absoluteTop - 2, element.absoluteLeft - 2
    238             else:
    239                 cumTop, cumLeft = element.parent.absoluteTop + element.top - 2, element.parent.absoluteLeft + element.left - 2
    240             pointLeft = True
    241             height = element.height if element.height else element.absoluteBottom - element.absoluteTop
    242             width = element.width if element.width else element.absoluteRight - element.absoluteLeft
    243             cumTop += height / 2 - pointerHeight / 2
    244             if defaultDirection in (UiHighlightDirections.UP, UiHighlightDirections.DOWN):
    245                 pointerLeft = cumLeft + width / 2 - pointerWidth / 2
    246             else:
    247                 pointerLeft = cumLeft - offset - pointerWidth
    248             if pointerLeft <= 0:
    249                 cumLeft = self._MoveToRightSide(element, directive, cumLeft) + offset
    250             else:
    251                 pointLeft = False
    252                 cumLeft = self._MoveToLeftSide(cumLeft, pointerWidth) - offset
    253             pointUp = False
    254             pointDown = False
    255             arrowPos = 0
    256             if cumTop < 0 or defaultDirection == UiHighlightDirections.UP and cumTop - pointerHeight / 2 < 0:
    257                 if directive == 'neocom':
    258                     additionalSpace = 8
    259                     arrowPos = cumTop - additionalSpace
    260                     cumTop = additionalSpace
    261                 else:
    262                     pointUp = True
    263                     cumTop, cumLeft = self._MoveToBottom(element, directive, cumTop, height, pointerWidth, pointerHeight)
    264                     cumTop += offset
    265             elif cumTop + pointerHeight > uicore.desktop.height:
    266                 if directive == 'neocom':
    267                     additionalSpace = 8
    268                     cumTop = uicore.desktop.height - pointerHeight - additionalSpace
    269                 else:
    270                     pointDown = True
    271                     cumTop, cumLeft = self._MoveToTop(pointToID, element, pointerWidth, pointerHeight)
    272                     cumTop -= offset
    273             elif pointLeft == False:
    274                 if directive == 'bracket':
    275                     cumTop -= 3
    276             elif directive == 'bracket':
    277                 cumLeft += POINTER_ARROW_WIDTH
    278                 cumTop -= 3
    279             if directive == 'eveMenuBtn':
    280                 cumTop, cumLeft, pointLeft, pointUp, pointDown, arrowPos = self.GetEveMenuLocation(pointerHeight, pointerWidth)
    281             if self._ShouldMoveToAuthoredSide(defaultDirection, pointLeft, pointUp, pointDown):
    282                 if defaultDirection == UiHighlightDirections.RIGHT:
    283                     newLeft = self._MoveToRightSide(element, directive, cumLeft) + pointerWidth
    284                     newLeft += 2 * offset
    285                     if self._IsLeftInView(newLeft, pointerWidth):
    286                         pointLeft = True
    287                         cumLeft = newLeft
    288                 elif defaultDirection == UiHighlightDirections.UP:
    289                     newTop, newLeft = self._MoveToTop(pointToID, element, pointerWidth, pointerHeight)
    290                     newTop -= offset
    291                     if self._IsInView(newLeft, newTop, pointerWidth, pointerHeight):
    292                         pointDown = True
    293                         cumLeft = newLeft
    294                         cumTop = newTop
    295                 elif defaultDirection == UiHighlightDirections.DOWN:
    296                     if uiPointer and uiPointer.uiPointerElement.pointDirections:
    297                         if not uiPointer.uiPointerElement.pointDirections[0]:
    298                             pointerHeight += POINTER_ARROW_HEIGHT
    299                     newTop, newLeft = self._MoveToBottom(element, directive, cumTop, height, pointerWidth, pointerHeight)
    300                     newTop += offset
    301                     if self._IsInView(newLeft, newTop, pointerWidth, pointerHeight):
    302                         pointUp = True
    303                         cumLeft = newLeft
    304                         cumTop = newTop
    305             if basicData.showEvenOffscreen:
    306                 cumLeft = mathext.clamp(cumLeft, 0, uicore.desktop.width - width)
    307                 cumTop = mathext.clamp(cumTop, 0, uicore.desktop.height - height)
    308             isObscured = self.CheckIsElementObscured(pointToID, cumTop, cumLeft, pointLeft, element)
    309             return (cumTop,
    310              cumLeft,
    311              pointLeft,
    312              pointUp,
    313              pointDown,
    314              isObscured,
    315              arrowPos)
    316         except:
    317             log.LogException('GetLocation encountered an Exception.')
    318             sys.exc_clear()
    319             return (-999,
    320              -999,
    321              False,
    322              False,
    323              False,
    324              False,
    325              0)
    326 
    327     def SkipElementDueToClipping(self, element, parent):
    328         if getattr(element, pConst.ALLOWS_POINTER_CLIPPING, False):
    329             return False
    330         clipped = element.IsCompletelyClipped(parent)
    331         return clipped
    332 
    333     def _IsInView(self, left, top, pointerWidth, pointerHeight):
    334         return self._IsLeftInView(left, pointerWidth) and self._IsTopInView(top, pointerHeight)
    335 
    336     def _IsLeftInView(self, left, pointerWidth):
    337         return left + pointerWidth < uicore.desktop.width
    338 
    339     def _IsTopInView(self, top, pointerHeight):
    340         return top + pointerHeight < uicore.desktop.height
    341 
    342     def _ShouldMoveToAuthoredSide(self, defaultDirection, pointLeft, pointUp, pointDown):
    343         pointRight = not (pointLeft or pointUp or pointDown)
    344         return defaultDirection and defaultDirection != UiHighlightDirections.LEFT and pointRight
    345 
    346     def _MoveToRightSide(self, element, directive, cumLeft):
    347         width = self._GetElementWidth(element, directive)
    348         return cumLeft + width + 2
    349 
    350     def _MoveToLeftSide(self, cumLeft, pointerWidth):
    351         return cumLeft - pointerWidth + 2
    352 
    353     def _MoveToTop(self, pointToID, element, pointerWidth, pointerHeight):
    354         pointer = self.uiPointersByPointToID.get(pointToID, None)
    355         if pointer is None:
    356             currentPointerHeight = 0
    357         else:
    358             uiPointerElement = pointer.uiPointerElement
    359             currentPointerHeight = uiPointerElement.height
    360             if currentPointerHeight < 1:
    361                 currentPointerHeight = pointerHeight
    362         newTop = element.absoluteTop - currentPointerHeight
    363         newLeft = element.absoluteLeft - pointerWidth / 2 + (element.absoluteRight - element.absoluteLeft) / 2
    364         if newLeft < 0:
    365             newLeft = 0
    366         elif newLeft + pointerWidth > uicore.desktop.width:
    367             newLeft = uicore.desktop.width - pointerWidth
    368         return (newTop, newLeft)
    369 
    370     def _MoveToBottom(self, element, directive, cumTop, height, pointerWidth, pointerHeight):
    371         newTop = cumTop + height / 2 + pointerHeight / 2
    372         if hasattr(element, 'absoluteLeft') and hasattr(element, 'absoluteRight'):
    373             newLeft = element.absoluteLeft - pointerWidth / 2 + (element.absoluteRight - element.absoluteLeft) / 2
    374         else:
    375             newLeft = element.parent.absoluteLeft + element.left - pointerWidth / 2
    376         if newLeft < 0:
    377             newLeft = 0
    378         elif newLeft + pointerWidth > uicore.desktop.width:
    379             newLeft = uicore.desktop.width - pointerWidth
    380         if directive == 'bracket':
    381             newTop -= 8
    382         return (newTop, newLeft)
    383 
    384     def _GetElementWidth(self, element, directive):
    385         if directive == 'neocom':
    386             neocom = sm.GetService('neocom').GetNeocomContainer()
    387             if neocom is not None:
    388                 width = neocom.width
    389             else:
    390                 width = element.width
    391                 if width == 0:
    392                     width = element.absoluteRight - element.absoluteLeft
    393         else:
    394             width = element.width
    395             if width < 1:
    396                 width = element.absoluteRight - element.absoluteLeft
    397         return width
    398 
    399     def GetEveMenuLocation(self, pointerHeight, pointerWidth):
    400         neocomIsLeftAligned = neocomSettings.neocom_align_setting.get() == uiconst.TOLEFT
    401         pointLeft = neocomIsLeftAligned
    402         pointUp = False
    403         pointDown = False
    404         eveMenuWidth, eveMenuHeight = self.neocomSvc.GetEveMenuButtonSize()
    405         cumTop = 0
    406         cumLeft = eveMenuWidth if neocomIsLeftAligned else uicore.desktop.width - eveMenuWidth - pointerWidth
    407         arrowPos = (eveMenuHeight - pointerHeight) / 2 if eveMenuHeight else 0
    408         return (cumTop,
    409          cumLeft,
    410          pointLeft,
    411          pointUp,
    412          pointDown,
    413          arrowPos)
    414 
    415     def CheckIsElementObscured(self, pointToID, top, left, pointLeft, element):
    416         globalLayer = uicore.layer.main
    417         abovemain = uicore.layer.abovemain
    418         candidates = self.GetObscureCandidates(globalLayer, element, False)
    419         candidates.extend(self.GetObscureCandidates(abovemain, element, True))
    420         left, top, width, height = element.GetAbsolute()
    421         elementPoints = []
    422         elementPoints.append(utillib.KeyVal(x=left, y=top))
    423         elementPoints.append(utillib.KeyVal(x=left, y=top + height))
    424         elementPoints.append(utillib.KeyVal(x=left + width, y=top))
    425         elementPoints.append(utillib.KeyVal(x=left + width, y=top + height))
    426         occluded = False
    427         occludors = []
    428         for candidate in candidates:
    429             absLeft, absTop, width, height = candidate.GetAbsolute()
    430             absRight = absLeft + width
    431             absBottom = absTop + height
    432             for point in elementPoints:
    433                 if point.x > absLeft and point.x < absRight and point.y > absTop and point.y < absBottom:
    434                     occluded = True
    435                     occludors.append(candidate)
    436                     break
    437 
    438         self.UpdateObscurers(pointToID, occludors)
    439         return occluded
    440 
    441     def GetObscureCandidates(self, layer, pointToElement, topLayer):
    442         parentWindow = self.GetElementsParent(pointToElement)
    443         parentIdx = self.GetElementIdx(parentWindow)
    444         if parentIdx is None:
    445             return []
    446         list = []
    447         for window in layer.children:
    448             windowIdx = self.GetElementIdx(window)
    449             if windowIdx is None:
    450                 return []
    451             if hasattr(window, 'name') and window.name not in ('UIPointer',
    452              'locationInfo',
    453              'snapIndicator',
    454              'windowhilite',
    455              parentWindow.name) and hasattr(window, 'state') and window.state != uiconst.UI_HIDDEN and hasattr(window, 'absoluteTop') and hasattr(window, 'absoluteBottom') and hasattr(window, 'absoluteRight') and hasattr(window, 'absoluteLeft'):
    456                 if not topLayer and windowIdx < parentIdx:
    457                     list.append(window)
    458                 elif topLayer:
    459                     list.append(window)
    460 
    461         return list
    462 
    463     def UpdateObscurers(self, pointToID, obscurers):
    464         oldObscurers = self.oldObscurers.get(pointToID, [])
    465         for window in oldObscurers:
    466             if window not in obscurers:
    467                 window.opacity = 1.0
    468 
    469         for window in obscurers:
    470             window.opacity = 0.6
    471 
    472         self.oldObscurers[pointToID] = obscurers
    473 
    474     def ClearObscurers(self, pointToID):
    475         oldObscurers = self.oldObscurers.pop(pointToID, [])
    476         for window in oldObscurers:
    477             window.opacity = 1.0
    478 
    479     def GetElementIdx(self, element):
    480         if element.name == 'aura9':
    481             return 0
    482         parent = element.parent
    483         if not parent:
    484             return None
    485         elementIndex = 0
    486         for child in parent.children:
    487             if child == element:
    488                 break
    489             elementIndex += 1
    490 
    491         return elementIndex
    492 
    493     def GetElementsParent(self, element):
    494         parentWindow = element
    495         while True:
    496             if not hasattr(parentWindow.parent, 'parent'):
    497                 break
    498             elif not hasattr(parentWindow.parent.parent, 'parent'):
    499                 break
    500             elif not hasattr(parentWindow.parent.parent.parent, 'parent'):
    501                 break
    502             else:
    503                 parentWindow = parentWindow.parent
    504 
    505         return parentWindow
    506 
    507     def FindElementToPointTo(self, elementUrl, shouldExcludeInvisible = False, blinkNeocom = True, findWindow = False):
    508         elementKeyVal = utillib.KeyVal(pointToElement=None)
    509         if not elementUrl:
    510             return elementKeyVal
    511         if isinstance(elementUrl, basestring):
    512             elementUrl = elementUrl.split('.')
    513         if len(elementUrl) == 1:
    514             elementName = elementUrl[0]
    515             if elementName in self.specialCasedPointers:
    516                 return self.FindSpecialCasedElements(elementName)
    517             pointToElement = self.FindDeep(uicore.desktop, elementName, shouldExcludeInvisible)
    518         elif len(elementUrl) == 2 and elementUrl[0] == 'neocom':
    519             wndID = elementUrl[1]
    520             pointToElement = self._FindNeocomeElementToPointTo(elementUrl, findWindow)
    521             neocomSvc = sm.GetService('neocom')
    522             if not neocomSvc.IsButtonVisible(wndID) and blinkNeocom:
    523                 neocomSvc.Blink(wndID)
    524         else:
    525             parent = uicore.desktop
    526             for path in elementUrl[:-1]:
    527                 parent = self.FindDeep(parent, path, shouldExcludeInvisible)
    528 
    529             pointToElement = self.FindDeep(parent, elementUrl[-1], shouldExcludeInvisible)
    530         if isinstance(pointToElement, Window) and pointToElement.stacked:
    531             pointToElement = pointToElement.sr.tab
    532         elementKeyVal.pointToElement = pointToElement
    533         return elementKeyVal
    534 
    535     def _FindNeocomeElementToPointTo(self, elementUrl, findWindow = False):
    536         wndID = elementUrl[1]
    537         neocomSvc = sm.GetService('neocom')
    538         pointToElement, node = neocomSvc.GetUIObjectAndNodeByID(wndID)
    539         if findWindow and node:
    540             newPointToElement = self._FindPointToElmentFromNode(node)
    541             if newPointToElement:
    542                 pointToElement = newPointToElement
    543         if pointToElement is None:
    544             pointToElement = neocomSvc.FindParentGroupBtn(wndID)
    545         if pointToElement is None:
    546             uniqueUiName = '.'.join(elementUrl)
    547             pointToElement = neocomSvc.GetUIObjectByUniqueUiName(uniqueUiName)
    548         if pointToElement:
    549             btnData = getattr(pointToElement, 'btnData', None)
    550             if neocomSvc.IsBtnInOverflowList(btnData):
    551                 pointToElement = neocomSvc.GetNeocomContainer().overflowBtn
    552         return pointToElement
    553 
    554     def _FindPointToElmentFromNode(self, node):
    555         wndClass = getattr(node, 'wndCls', None)
    556         if not wndClass:
    557             return
    558         wnd = wndClass.GetIfOpen()
    559         if wnd and not wnd.destroyed and not wnd.IsMinimized() and wnd.IsVisible():
    560             if wnd.stacked:
    561                 return wnd.stack
    562             else:
    563                 return wnd
    564 
    565     def PointTo(self, basicData):
    566         self.LogInfo('PointTo', basicData.pointToID, basicData.uiPointerText, basicData.uiPointerTitle)
    567         if basicData.pointToID is None or basicData.pointToID == '':
    568             return
    569         self.uiElementsToHighlight.add(basicData.pointToID)
    570         if self.uiPointerUpdateThread is None:
    571             self.uiPointerUpdateThread = AutoTimer(20, self.UpdatePointers)
    572         uthread.new(self._PointToThread, basicData)
    573         sm.ScatterEvent('OnUiHighlightCreated', basicData.pointToID)
    574 
    575     def _PointToThread(self, basicData):
    576         while basicData.pointToID in self.uiElementsToHighlight and basicData.pointToID not in self.uiPointersByPointToID:
    577             self._PointToElementByID(basicData)
    578             blue.pyos.synchro.SleepWallclock(20)
    579 
    580     def StopPointingTo(self, pointToID):
    581         if pointToID not in self.uiElementsToHighlight:
    582             return
    583         self.uiElementsToHighlight.remove(pointToID)
    584         self.ClearPointer(pointToID)
    585         self.elementUrls.pop(pointToID, None)
    586         data = self.uiPointersByPointToID.pop(pointToID, None)
    587         if data and data.basicData.highlightContent:
    588             data.basicData.highlightContent.Close()
    589         sm.ScatterEvent('OnUiHighlightDeleted', pointToID)
    590 
    591     def IsAnyPointerActive(self):
    592         return bool(self.uiElementsToHighlight)
    593 
    594     def _GetConsiderationsForPointToElement(self, pointToID, pointToElement):
    595         considerations = None
    596         elementUrl = self.elementUrls.get(pointToID, None)
    597         if elementUrl and elementUrl[0] in ('neocom', pConst.UNIQUE_NAME_CLOCK_CALENDAR):
    598             considerations = 'neocom'
    599         elif elementUrl and elementUrl[0].replace(pConst.UNIQUE_UI_PREFIX, '') == 'eveMenuBtn':
    600             considerations = 'eveMenuBtn'
    601         parent = pointToElement
    602         while considerations is None and getattr(parent, 'parent', None) and hasattr(parent.parent, 'name'):
    603             parent = parent.parent
    604             if parent.name == 'shipui':
    605                 considerations = 'shipui'
    606             elif parent.name == 'l_bracket':
    607                 considerations = 'bracket'
    608 
    609         return considerations
    610 
    611     def _PointToElementByID(self, basicData):
    612         pointToID = basicData.pointToID
    613         if pointToID in self.uiPointersByPointToID:
    614             return
    615         self.elementUrls[pointToID] = pointToID.split('.')
    616         if not self.ShouldHidePointers():
    617             elementUrl = self.elementUrls.get(pointToID, None)
    618             pointToElement = None
    619             if basicData.shouldPrioritizeVisible:
    620                 elementKeyVal = self.FindElementToPointTo(elementUrl, shouldExcludeInvisible=True)
    621                 pointToElement = elementKeyVal.pointToElement if elementKeyVal else None
    622             if not pointToElement:
    623                 elementKeyVal = self.FindElementToPointTo(elementUrl)
    624                 pointToElement = elementKeyVal.pointToElement if elementKeyVal else None
    625             if pointToElement is not None and pointToElement.state != uiconst.UI_HIDDEN:
    626                 considerations = self._GetConsiderationsForPointToElement(pointToID, pointToElement)
    627                 locationData = self.GetLocation(basicData, pointToElement, considerations)
    628                 cumTop, cumLeft, pointLeft, pointUp, pointDown, isObscured, arrowPos = locationData
    629                 elementContainer = self.ConstructPointer(pointLeft, pointUp, pointDown, arrowPos, basicData, pointToElement)
    630                 self.uiPointersByPointToID[pointToID] = UiPointerData(uiPointerElement=elementContainer, pointToElement=pointToElement, considerations=considerations, oldPointLeft=pointLeft, oldPointUp=pointUp, oldPointDown=pointDown, basicData=basicData)
    631                 self._UpdatePointer(pointToID)
    632                 self.statelogger.log_visible(pointToID)
    633             elif pointToElement is None:
    634                 self.statelogger.log_element_not_found(pointToID)
    635             else:
    636                 self.statelogger.log_element_invisible(pointToID)
    637 
    638     def ClearPointer(self, pointToID):
    639         pointer = self.uiPointersByPointToID.get(pointToID, None)
    640         if pointer is not None:
    641             element = pointer.uiPointerElement
    642             element.Close()
    643             if element in uicore.layer.hint.children:
    644                 uicore.layer.hint.children.remove(element)
    645         self.ClearObscurers(pointToID)
    646         self.statelogger.clear_pointer(pointToID)
    647 
    648     def ClearPointers(self):
    649         for pointToID in self.uiElementsToHighlight.copy():
    650             self.StopPointingTo(pointToID)
    651 
    652         self.uiPointersByPointToID.clear()
    653 
    654     def RestartUiPointer(self, pointToID):
    655         pointer = self.uiPointersByPointToID.get(pointToID, None)
    656         if not pointer:
    657             return
    658         basicData = pointer.basicData
    659         self.StopPointingTo(pointToID)
    660         self.PointTo(basicData)
    661 
    662     def GetUiPointerClass(self, basicData):
    663         if basicData.highlightContent:
    664             return CustomUiPointer
    665         uiClass = basicData.uiClass
    666         if uiClass in UI_CLASS_BY_NAME:
    667             return UI_CLASS_BY_NAME[uiClass]
    668         return DEFAULT_UI_CLASS
    669 
    670     def ConstructPointer(self, pointLeft, pointUp, pointDown, arrowPosition, basicData, pointToElement):
    671         layer = uicore.layer.hint
    672         if basicData.audioSetting:
    673             uihighlightaudio.manager.play_highlight_appeared_sound()
    674         rectTop = 128
    675         if pointLeft:
    676             rectTop = 0
    677         defaultWidth = UIPOINTER_WIDTH
    678         defaultHeight = UIPOINTER_HEIGHT
    679         if pointUp or pointDown:
    680             defaultHeight -= POINTER_ARROW_WIDTH
    681         else:
    682             defaultWidth -= POINTER_ARROW_HEIGHT
    683         uiPointerClass = self.GetUiPointerClass(basicData)
    684         idx = basicData.idx if basicData.idx is not None else -1
    685         uiPointer = uiPointerClass(parent=layer, name='uiPointer', idx=idx, width=defaultWidth, height=defaultHeight, align=uiconst.TOPLEFT, state=uiconst.UI_DISABLED, rectTop=rectTop, pointDirections=(pointUp, pointDown, pointLeft), arrowPositionModifier=arrowPosition, useArrowPointer=True, basicData=basicData, highlightContent=basicData.highlightContent, pointToElement=pointToElement)
    686         uiPointer.ConstructContent()
    687         return uiPointer
    688 
    689     def HidePointer(self, pointToID):
    690         pointer = self.uiPointersByPointToID.get(pointToID, None)
    691         if pointer is not None:
    692             self.statelogger.log_hidden(pointToID)
    693             pointer.uiPointerElement.Hide()
    694 
    695     def ShowPointer(self, pointToID):
    696         pointer = self.uiPointersByPointToID.get(pointToID, None)
    697         if pointer is not None:
    698             self.statelogger.log_visible(pointToID)
    699             pointer.uiPointerElement.Show()
    700 
    701     def _AddSpaceObjectTypeUiPointer(self, typeID, groupID, message, hint, audioSetting, localize = True, itemID = None, objectID = None, entityGroupID = None, showBox = True, highlightBracket = True):
    702         self.LogInfo('AddSpaceObjectTypeUiPointer', typeID, groupID, message, hint, localize, itemID, objectID)
    703         with self.spaceObjectPointerLock:
    704             if itemID is not None and itemID not in self.pointerUIByItemID:
    705                 self.pointerDataByItemID[itemID] = SpaceObjectUiPointerData(None, None, itemID, message, hint, audioSetting, localize, showBox, highlightBracket)
    706             elif objectID is not None and objectID not in self.pointerDataByDungeonObjectID:
    707                 self.pointerDataByDungeonObjectID[objectID] = SpaceObjectUiPointerData(None, None, None, message, hint, audioSetting, localize, showBox, highlightBracket)
    708             elif entityGroupID is not None and entityGroupID not in self.pointerDataByDungeonEntityGroupID:
    709                 self.pointerDataByDungeonEntityGroupID[entityGroupID] = SpaceObjectUiPointerData(None, None, None, message, hint, audioSetting, localize, showBox, highlightBracket)
    710             elif typeID is not None and typeID not in self.pointerDataByTypeID:
    711                 self.pointerDataByTypeID[typeID] = SpaceObjectUiPointerData(typeID, None, None, message, hint, audioSetting, localize, showBox, highlightBracket)
    712             elif groupID is not None and groupID not in self.pointerDataByByGroupID:
    713                 self.pointerDataByByGroupID[groupID] = SpaceObjectUiPointerData(None, groupID, None, message, hint, audioSetting, localize, showBox, highlightBracket)
    714         self.RestartSpacePointers(clearPointers=False)
    715 
    716     def AddSpaceObjectTypeUiPointer(self, typeID, groupID, message, hint, audioSetting, localize = True, itemID = None, objectID = None, entityGroupID = None, showBox = True, highlightBracket = True):
    717         uthread.new(self._AddSpaceObjectTypeUiPointer, typeID, groupID, message, hint, audioSetting, localize=localize, itemID=itemID, objectID=objectID, entityGroupID=entityGroupID, showBox=showBox, highlightBracket=highlightBracket)
    718 
    719     def AddNextInRouteUiPointer(self, message, hint, audioSetting, showBox = True, highlightBracket = True):
    720         self.pointerDataNextInRoute = SpaceObjectUiPointerData(None, None, message, hint, audioSetting, False, showBox, highlightBracket)
    721         self.RestartSpacePointers(clearPointers=False)
    722 
    723     def RestartSpacePointers(self, clearPointers = False):
    724         self.KillSpacePointerUpdater()
    725         if clearPointers:
    726             for itemID in self.pointerUIByItemID.keys():
    727                 self.FlushSpaceObjectPointer(itemID)
    728 
    729         self.StartPointerUpdater()
    730 
    731     def StartPointerUpdater(self):
    732         if self.spaceObjectUiPointerUpdater is None and any([bool(self.pointerDataByItemID),
    733          bool(self.pointerDataByTypeID),
    734          bool(self.pointerDataByByGroupID),
    735          bool(self.pointerDataByDungeonObjectID),
    736          bool(self.pointerDataByDungeonEntityGroupID)]):
    737             self.spaceObjectUiPointerUpdater = uthread.new(self.UpdateSpaceObjectUiPointersThread)
    738 
    739     def KillSpacePointerUpdater(self):
    740         if self.spaceObjectUiPointerUpdater is not None:
    741             self.spaceObjectUiPointerUpdater.kill()
    742             self.spaceObjectUiPointerUpdater = None
    743 
    744     def UpdateSpaceObjectUiPointersThread(self):
    745         blue.pyos.synchro.SleepWallclock(HINT_DISPLAY_DELAY_MS)
    746         while self.spaceObjectUiPointerUpdater is not None:
    747             if not self._UpdateSpaceObjectUiPointers():
    748                 return
    749             blue.pyos.synchro.SleepWallclock(HINT_WORKER_DELAY_MS)
    750 
    751     def _UpdateSpaceObjectUiPointers(self):
    752         bp = self.michelle.GetBallpark()
    753         if bp is None:
    754             return False
    755         with self.spaceObjectPointerLock:
    756             self.CheckAddOrRemovePointers(bp)
    757         return True
    758 
    759     def CheckAddOrRemovePointers(self, bp):
    760         for slimItem in bp.slimItems.values():
    761             if slimItem.charID:
    762                 continue
    763             elif slimItem.itemID in self.suppressedSpaceObjectUiPointers:
    764                 continue
    765             data = self._GetPointerData(slimItem)
    766             if data is not None:
    767                 if slimItem.itemID not in self.pointerUIByItemID:
    768                     self.pointerUIByItemID[slimItem.itemID] = SpaceObjectPointer(slimItem, data)
    769                     self.LogInfo('Creating UI Pointer for item', slimItem.itemID, 'of type', slimItem.typeID)
    770                     sm.ScatterEvent('OnItemHighlighted', slimItem.itemID, True)
    771             elif slimItem.itemID in self.pointerUIByItemID:
    772                 self.FlushSpaceObjectPointer(slimItem.itemID)
    773 
    774     def _GetPointerData(self, slimItem):
    775         dunObjectID = getattr(slimItem, 'dunObjectID', None)
    776         dunObjectNameID = getattr(slimItem, 'dunObjectNameID', None)
    777         dunEntityGroupID = getattr(slimItem, 'entityGroupID', None)
    778         data = self.pointerDataByItemID.get(slimItem.itemID, None)
    779         if data is None and dunObjectID:
    780             data = self.pointerDataByDungeonObjectID.get(dunObjectID)
    781         if data is None:
    782             data = self.pointerDataByDungeonEntityGroupID.get(dunEntityGroupID)
    783         if data is None:
    784             data = self.pointerDataByTypeID.get(slimItem.typeID)
    785         if data is None:
    786             data = self.pointerDataByByGroupID.get(slimItem.groupID)
    787         if data is None and self.pointerDataNextInRoute and slimItem.itemID == sm.GetService('autoPilot').GetNextItemIDInRoute():
    788             data = self.pointerDataNextInRoute
    789         return data
    790 
    791     def _RemoveSpaceObjectUiPointers(self):
    792         self.KillSpacePointerUpdater()
    793         with self.spaceObjectPointerLock:
    794             self.pointerDataByItemID.clear()
    795             self.pointerDataByDungeonObjectID.clear()
    796             self.pointerDataByDungeonEntityGroupID.clear()
    797             self.pointerDataByTypeID.clear()
    798             self.pointerDataByByGroupID.clear()
    799             self.pointerDataNextInRoute = None
    800             self.suppressedSpaceObjectUiPointers.clear()
    801             for pointerID in self.pointerUIByItemID.keys():
    802                 self.FlushSpaceObjectPointer(pointerID)
    803 
    804         self.LogInfo('Removing all space object pointers')
    805 
    806     def RemoveSpaceObjectUiPointers(self):
    807         uthread.new(self._RemoveSpaceObjectUiPointers)
    808 
    809     def _RemoveSpaceObjectUiPointersByAttribute(self, attributeName, attributeValue):
    810         self.KillSpacePointerUpdater()
    811         pointersByAttribute = {'itemID': self.pointerDataByItemID,
    812          'typeID': self.pointerDataByTypeID,
    813          'groupID': self.pointerDataByByGroupID,
    814          'dunObjectID': self.pointerDataByDungeonObjectID,
    815          'dunEntityGroupID': self.pointerDataByDungeonEntityGroupID}
    816         with self.spaceObjectPointerLock:
    817             allAttributePointers = pointersByAttribute[attributeName]
    818             pointersToRemove = []
    819             for pointer in allAttributePointers.values():
    820                 attributeValueInPointer = getattr(pointer, attributeName, None)
    821                 if attributeValueInPointer is not None and attributeValueInPointer == attributeValue:
    822                     pointersToRemove.append(pointer)
    823 
    824             if attributeValue in allAttributePointers:
    825                 del allAttributePointers[attributeValue]
    826             for pointer in pointersToRemove:
    827                 itemID = pointer.itemID
    828                 self.FlushSpaceObjectPointer(itemID)
    829                 if itemID in self.suppressedSpaceObjectUiPointers:
    830                     self.suppressedSpaceObjectUiPointers.remove(itemID)
    831 
    832         self._UpdateSpaceObjectUiPointers()
    833         self.StartPointerUpdater()
    834 
    835     def RemoveSpaceObjectUiPointersByItem(self, itemID):
    836         uthread.new(self._RemoveSpaceObjectUiPointersByAttribute, 'itemID', itemID)
    837 
    838     def RemoveSpaceObjectUiPointersByDungeonObject(self, objectID):
    839         uthread.new(self._RemoveSpaceObjectUiPointersByAttribute, 'dunObjectID', objectID)
    840 
    841     def RemoveSpaceObjectUiPointersByDungeonEntityGroup(self, entityGroupID):
    842         uthread.new(self._RemoveSpaceObjectUiPointersByAttribute, 'dunEntityGroupID', entityGroupID)
    843 
    844     def RemoveSpaceObjectUiPointersByType(self, typeID):
    845         uthread.new(self._RemoveSpaceObjectUiPointersByAttribute, 'typeID', typeID)
    846 
    847     def RemoveSpaceObjectUiPointersByGroup(self, groupID):
    848         uthread.new(self._RemoveSpaceObjectUiPointersByAttribute, 'groupID', groupID)
    849 
    850     def FlushSpaceObjectPointer(self, itemID):
    851         pointer = self.pointerUIByItemID.pop(itemID, None)
    852         if pointer is not None:
    853             pointer.Close()
    854             sm.ScatterEvent('OnItemHighlighted', itemID, False)
    855 
    856     def SuppressSpaceObjectPointer(self, itemID):
    857         self.suppressedSpaceObjectUiPointers.add(itemID)
    858         self.LogInfo('space object pointer for', itemID, 'was suppressed')
    859         self.FlushSpaceObjectPointer(itemID)
    860 
    861     @telemetry.ZONE_METHOD
    862     def DoBallsRemove(self, pythonBalls, isRelease):
    863         for ball, slimItem, terminal in pythonBalls:
    864             self.DoBallRemove(ball, slimItem, terminal)
    865 
    866     def DoBallRemove(self, ball, slimItem, terminal):
    867         self.FlushSpaceObjectPointer(slimItem.itemID)
    868 
    869     def OnInvItemDropEnded(self, name):
    870         if name:
    871             self.RestartUiPointer(name)
    872 
    873     def FindSpecialCasedElements(self, elementName):
    874         if elementName == pConst.UNIQUE_NAME_NEXT_IN_ROUTE:
    875             from eve.client.script.ui.inflight.overview.overviewWindowUtil import GetOverviewWndIfOpen
    876             overview = GetOverviewWndIfOpen()
    877             if not overview:
    878                 return
    879             panel, direction = overview.GetNextInRouteNodeAndDirection()
    880             textList = None
    881             if direction == 'up':
    882                 textList = [GetByLabel('UI/Overview/HelpPointerScrollUp')]
    883             elif direction == 'down':
    884                 textList = [GetByLabel('UI/Overview/HelpPointerScrollDown')]
    885             return utillib.KeyVal(pointToElement=panel, extraTextLines=textList)
    886         helpPointer = sm.GetService('helpPointer')
    887         if elementName == pConst.UNIQUE_NAME_LOCAL_CHAT:
    888             return utillib.KeyVal(pointToElement=helpPointer.FindLocalChatWindow(elementName))
    889         if elementName == pConst.UNIQUE_NAME_CORP_CHAT:
    890             return utillib.KeyVal(pointToElement=helpPointer.FindCorpChatWindow(elementName))
    891         if elementName == pConst.UNIQUE_NAME_FLEET_CHAT:
    892             return utillib.KeyVal(pointToElement=helpPointer.FindFleetChatWindow(elementName))
    893         return utillib.KeyVal(pointToElement=None)