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

surveyUI.py (32990B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\shared\planet\surveyUI.py
      2 import math
      3 import blue
      4 import evetypes
      5 import geo2
      6 import localization
      7 import uthread
      8 from carbonui import fontconst, uiconst
      9 from carbonui.control.slider import Slider
     10 from carbonui.control.window import Window
     11 from carbonui.primitives.container import Container
     12 from carbonui.primitives.containerAutoSize import ContainerAutoSize
     13 from carbonui.primitives.fill import Fill
     14 from carbonui.uianimations import animations
     15 from carbonui.uicore import uicore
     16 from carbonui.util.color import Color
     17 from eve.client.script.ui.control import eveIcon, eveLabel
     18 from eve.client.script.ui.control.bargraph import BarGraph
     19 from carbonui.control.buttonIcon import ButtonIcon
     20 from carbonui.control.button import Button
     21 from eve.client.script.ui.control.eveLabel import EveLabelSmall
     22 from eve.client.script.ui.control.gauge import Gauge
     23 from eve.client.script.ui.control.infoIcon import MoreInfoIcon
     24 from eve.client.script.ui.shared.planet import planetCommon as planetCommonUI
     25 from eve.client.script.ui.shared.planet.pinContainers.BasePinContainer import CaptionLabel, IconButton
     26 from eve.common.lib import appConst
     27 from eve.common.lib.PlanetResources import builder
     28 from eve.common.script.util import planetCommon
     29 from localization import GetByLabel
     30 from localization.formatters.timeIntervalFormatters import FormatTimeIntervalShortWritten
     31 BTNSIZE = 16
     32 
     33 class SurveyWindow(Window):
     34     __notifyevents__ = ['OnPlanetViewChanged', 'OnEditModeChanged', 'OnEditModeBuiltOrDestroyed']
     35     default_fixedWidth = 730
     36     default_fixedHeight = 320
     37     default_windowID = 'PlanetSurvey'
     38 
     39     def ApplyAttributes(self, attributes):
     40         self.default_fixedHeight *= fontconst.fontSizeFactor
     41         super(SurveyWindow, self).ApplyAttributes(attributes)
     42         self.ecuPinID = attributes.get('ecuPinID')
     43         self.barGraphUpdating = False
     44         self.planetUISvc = sm.GetService('planetUI')
     45         self.planet = self.planetUISvc.planet
     46         self.pin = self.planet.GetPin(self.ecuPinID)
     47         self.extractionHeadValues = {}
     48         self.resourceTypeIDs = self.planet.remoteHandler.GetPlanetResourceInfo().keys()
     49         self.currResourceTypeID = self.pin.programType
     50         self.outputVals = []
     51         self.programCycleTime = None
     52         self.dragThread = None
     53         self.currCycleTime = None
     54         self.UpdateCurrentRadius()
     55         self.sh = None
     56         if self.currResourceTypeID is not None:
     57             inRange, self.sh = self.planetUISvc.planet.GetResourceData(self.currResourceTypeID)
     58         self.revertToResource = self.planetUISvc.selectedResourceTypeID
     59         self.currentResource = None
     60         self.editsEnabled = self.pin.GetExtractionType() is None or self.pin.GetTimeToExpiry() <= 0
     61         if self.editsEnabled:
     62             self.planetUISvc.myPinManager.UnlockHeads(self.pin.id)
     63         self.barTimeIndicatorThread = None
     64         self.overlapValues = {}
     65         self.stateColor = None
     66         self.pinData = None
     67         if self.pin.IsInEditMode():
     68             self.pinData = self.pin.Serialize()
     69         self.MakeUnResizeable()
     70         captionTxt = localization.GetByLabel('UI/PI/Common/SurveyingProgram', pinName=planetCommon.GetGenericPinName(self.pin.typeID, self.pin.id))
     71         self.SetCaption(captionTxt)
     72         Container(parent=self.sr.main, align=uiconst.TOTOP, height=10)
     73         self.leftCont = Container(name='leftCont', parent=self.sr.main, align=uiconst.TOLEFT, width=130, padding=(8, 0, 3, 6))
     74         self.rightCont = Container(name='rightCont', parent=self.sr.main, align=uiconst.TORIGHT, width=160, padding=(10, 0, 6, 6))
     75         self.centerCont = Container(name='centerCont', parent=self.sr.main, align=uiconst.TOALL)
     76         self._ConstructLeftContainer()
     77         self._ConstructCenterContainer()
     78         self._ConstructRightContainer()
     79         self.SetCurrentResource(self.currResourceTypeID)
     80         self.UpdateSubmitButton()
     81 
     82     def _ConstructLeftContainer(self):
     83         CaptionLabel(parent=self.leftCont, text=localization.GetByLabel('UI/PI/Common/ExtractorHeadUnits'), align=uiconst.TOTOP)
     84         self.ConstructExtractorHeadBtns()
     85         self.noExtractorsLabel = CaptionLabel(name='noExtractorsLabel', parent=self.leftCont, text=localization.GetByLabel('UI/PI/Common/NoExtractorsInstalled'), align=uiconst.TOTOP, padTop=4, color=Color.RED, state=uiconst.UI_HIDDEN)
     86         self.UpdateNoExtractorsLabel()
     87         MoreInfoIcon(parent=self.leftCont, align=uiconst.BOTTOMLEFT, hint=GetByLabel('UI/PI/Common/ECUHelpText'))
     88 
     89     def UpdateNoExtractorsLabel(self):
     90         if not self.pin.GetNumHeads():
     91             self.noExtractorsLabel.Show()
     92         else:
     93             self.noExtractorsLabel.Hide()
     94 
     95     def UpdateCurrentRadius(self):
     96         self.currentRadius = max(min(self.pin.headRadius, planetCommon.RADIUS_DRILLAREAMAX), planetCommon.RADIUS_DRILLAREAMIN)
     97 
     98     def ConstructExtractorHeadBtns(self):
     99         self.headButtons = []
    100         headEntryCont = Container(parent=self.leftCont, name='headEntryCont', align=uiconst.TOTOP, height=140, padTop=4)
    101         headEntryContLeft = Container(parent=headEntryCont, name='headEntryContLeft', align=uiconst.TOLEFT, width=self.leftCont.width / 2)
    102         headEntryContRight = Container(parent=headEntryCont, name='headEntryContRight', align=uiconst.TOLEFT, width=self.leftCont.width / 2)
    103         for i in xrange(planetCommon.ECU_MAX_HEADS):
    104             value = self.GetResourceLayerValueForHead(i)
    105             state = uiconst.UI_NORMAL if self.editsEnabled else uiconst.UI_DISABLED
    106             parent = headEntryContLeft if i < 5 else headEntryContRight
    107             btn = ExtractionHeadEntry(parent=parent, headID=i, ecuID=self.pin.id, align=uiconst.TOTOP, height=20, state=state, value=value, pin=self.pin)
    108             btn.OnClick = (self.OnHeadBtnClicked, i)
    109             self.headButtons.append(btn)
    110 
    111     def ConstructAreaSliderAndButtons(self):
    112         sliderParCont = ContainerAutoSize(name='sliderParCont', parent=self.rightCont, align=uiconst.TOTOP, padTop=10)
    113         CaptionLabel(parent=sliderParCont, text=localization.GetByLabel('UI/PI/Common/ProgramDuration'), align=uiconst.TOTOP, padBottom=2)
    114         sliderCont = ContainerAutoSize(parent=sliderParCont, name='sliderCont', align=uiconst.TOTOP, alignMode=uiconst.TOTOP)
    115         subtrBtn = ButtonIcon(parent=sliderCont, name='subtractBtn', align=uiconst.CENTERLEFT, texturePath='res:/UI/Texture/Icons/minus.png', iconSize=12, func=self.DecreaseDrillAreaSize, width=BTNSIZE)
    116         addBtn = ButtonIcon(parent=sliderCont, name='addBtn', align=uiconst.CENTERRIGHT, texturePath='res:/UI/Texture/Icons/plus.png', iconSize=12, func=self.IncreaseDrillAreaSize, width=BTNSIZE)
    117         sliderValue = math.sqrt((self.currentRadius - planetCommon.RADIUS_DRILLAREAMIN) / planetCommon.RADIUS_DRILLAREADIFF)
    118         self.programDurationSlider = Slider(parent=sliderCont, name='programDurationSlider', align=uiconst.TOTOP, minValue=0.0, maxValue=1.0, value=sliderValue, hint=localization.GetByLabel('UI/PI/Common/ExtractionAreaSize'), callback=self.OnProgramDurationSliderReleased, on_dragging=self.OnProgramDurationSliderMoved, padding=(BTNSIZE + 4,
    119          0,
    120          BTNSIZE + 4,
    121          0))
    122         self.programDurationSlider.barCont.OnMouseWheel = self.OnProgramDurationSliderMouseWheel
    123         if not self.editsEnabled:
    124             self.programDurationSlider.Disable()
    125             subtrBtn.Disable()
    126             addBtn.Disable()
    127         self.programDurationLabel = EveLabelSmall(name='programDurationLabel', parent=sliderParCont, align=uiconst.TOTOP, padTop=2)
    128         self.UpdateProgramDurationLabel()
    129 
    130     def UpdateProgramDurationLabel(self):
    131         self.programDurationLabel.text = self.GetProgramDurationText()
    132 
    133     def DecreaseDrillAreaSize(self, *args):
    134         self.ChangeDrillAreaSize(numSteps=-1)
    135 
    136     def IncreaseDrillAreaSize(self, *args):
    137         self.ChangeDrillAreaSize(numSteps=1)
    138 
    139     def ChangeDrillAreaSize(self, numSteps):
    140         newSliderValue = self._GetNextSliderValue(numSteps)
    141         self.programDurationSlider.SetValue(newSliderValue)
    142         self.UpdateProgramDuration()
    143         self.UpdateDurationLabels()
    144 
    145     def _GetNextSliderValue(self, numSteps):
    146         oldNumCycles = None
    147         sliderVal = self.programDurationSlider.value
    148         maxCount = 1000
    149         count = 0
    150         while count < maxCount:
    151             count += 1
    152             cycleTime, numCycles = self._GetCycleTimeAndNumCyclesFromSliderVal(sliderVal)
    153             if not oldNumCycles:
    154                 oldNumCycles = numCycles
    155             if oldNumCycles != numCycles:
    156                 break
    157             sliderVal += 0.001 * numSteps
    158 
    159         return sliderVal
    160 
    161     def _GetCycleTimeAndNumCyclesFromSliderVal(self, sliderVal):
    162         radius = self._GetRadiusFromSliderVal(sliderVal)
    163         programLength = planetCommon.GetProgramLengthFromHeadRadius(radius)
    164         cycleTime = planetCommon.GetCycleTimeFromProgramLength(programLength)
    165         numCycles = int(programLength / cycleTime)
    166         return (cycleTime, numCycles)
    167 
    168     def _GetRadiusFromSliderVal(self, sliderVal):
    169         return sliderVal ** 2 * planetCommon.RADIUS_DRILLAREADIFF + planetCommon.RADIUS_DRILLAREAMIN
    170 
    171     def OnProgramDurationSliderReleased(self, slider):
    172         self.UpdateProgramDuration()
    173         self.UpdateDurationLabels()
    174 
    175     def UpdateProgramDuration(self, updateBargraph = True):
    176         radius = self.programDurationSlider.value ** 2 * planetCommon.RADIUS_DRILLAREADIFF + planetCommon.RADIUS_DRILLAREAMIN
    177         self.planetUISvc.myPinManager.SetExtractionHeadRadius(self.pin.id, radius)
    178         self.currentRadius = radius
    179         self.UpdateProgram(updateBargraph=updateBargraph)
    180 
    181     def OnProgramDurationSliderMoved(self, slider):
    182         self.UpdateDurationLabels()
    183         self.UpdateProgramDuration(updateBargraph=False)
    184 
    185     def UpdateDurationLabels(self):
    186         text = self.GetProgramDurationText()
    187         self.barGraph.SetXLabels((localization.GetByLabel('UI/PI/Common/SurveyProgramStart'), text))
    188         self.programDurationLabel.text = text
    189 
    190     def GetProgramDurationText(self):
    191         cycleTime, numCycles = self._GetCycleTimeAndNumCyclesFromSliderVal(self.programDurationSlider.value)
    192         cycleTime = int(cycleTime * const.HOUR)
    193         text = self._GetProgramDurationText(cycleTime, numCycles)
    194         return text
    195 
    196     def _GetProgramDurationText(self, cycleTime, numCycles):
    197         return FormatTimeIntervalShortWritten(cycleTime * numCycles, showTo='minute')
    198 
    199     def OnProgramDurationSliderMouseWheel(self, dz, *args):
    200         self.ChangeDrillAreaSize(int(dz / 120))
    201 
    202     def _ConstructCenterContainer(self):
    203         self.barGraph = BarGraph(parent=self.centerCont, align=uiconst.TOALL, barUpdateDelayMs=1, barHintFunc=self.GetBarHint, padding=(0, 2, 0, 10))
    204         uthread.new(self.UpdateProgram)
    205 
    206     def GetBarHint(self, numBar, value, maxValue):
    207         accOutput = sum(self.outputVals[:numBar])
    208         accTime = self.currCycleTime * numBar
    209         accPerHour = appConst.HOUR * accOutput / accTime
    210         return localization.GetByLabel('UI/PI/Common/SurveyBarHint', numBar=numBar, value=value, accOutput=accOutput, accTime=accTime, accPerHour=accPerHour)
    211 
    212     def UpdateProgram(self, replaceHeadID = None, point = None, updateBargraph = True):
    213         if self.currentResource is None:
    214             return
    215         cycleTime, maxValue, numCycles = self.GetProgramParameters(point, replaceHeadID)
    216         self.currCycleTime = cycleTime
    217         self.programCycleTime = cycleTime
    218         self.outputVals = self.pin.GetProgramOutputPrediction(maxValue, cycleTime, numCycles)
    219         self._UpdateOutputPerHourLabel(numCycles)
    220         if updateBargraph:
    221             self._UpdateBargraph(numCycles)
    222 
    223     def GetProgramParameters(self, point, replaceHeadID):
    224         if self.editsEnabled:
    225             heads = self.pin.heads[:]
    226             if replaceHeadID is not None:
    227                 for each in self.pin.heads:
    228                     if each[0] == replaceHeadID:
    229                         heads.remove(each)
    230                         heads.append((replaceHeadID, point.phi, point.theta))
    231                         break
    232 
    233             colony = self.GetColony()
    234             maxValue, cycleTime, numCycles, self.overlapValues = colony.CreateProgram(self.currentResource, self.pin.id, self.currResourceTypeID, points=heads, headRadius=self.currentRadius)
    235             self.UpdateOverlapValues()
    236             self.planetUISvc.myPinManager.SetEcuOverlapValues(self.pin.id, self.overlapValues)
    237         else:
    238             maxValue, cycleTime, numCycles = self.pin.GetProgramParameters()
    239         return (cycleTime, maxValue, numCycles)
    240 
    241     def _UpdateBargraph(self, numCycles):
    242         self.barGraph.SetValues(self.outputVals)
    243         xLabels = (localization.GetByLabel('UI/PI/Common/SurveyProgramStart'), self._GetProgramDurationText(self.programCycleTime, numCycles))
    244         self.barGraph.SetXLabels(xLabels)
    245         self.UpdateBarTimeIndicator()
    246 
    247     def UpdateOverlapValues(self):
    248         for id, headButton in enumerate(self.headButtons):
    249             headButton.SetOverlapValue(self.overlapValues.get(id, None))
    250 
    251     def _UpdateOutputPerHourLabel(self, numCycles):
    252         totalOutput = sum(self.outputVals)
    253         perHourOutput = float(totalOutput) * appConst.HOUR / (numCycles * self.programCycleTime)
    254         if self.currResourceTypeID:
    255             self.outputPerHourTxt.Show()
    256             self.outputPerHourTxt.SetText(localization.GetByLabel('UI/PI/Common/SurveyPerHourOutput', perHourOutput=perHourOutput))
    257             self.outputTotalTxt.SetText(localization.GetByLabel('UI/PI/Common/SurveyTotalOutput', totalOutput=totalOutput))
    258         else:
    259             self.outputPerHourTxt.Hide()
    260 
    261     def UpdateBarTimeIndicator(self):
    262         if self.pin.IsActive() and not self.pin.IsInEditMode():
    263             if self.barTimeIndicatorThread is None:
    264                 self.barTimeIndicatorThread = uthread.new(self._UpdateBarTimeIndicator)
    265         else:
    266             self.barGraph.HideTimeIndicator()
    267             if self.barTimeIndicatorThread is not None:
    268                 self.barTimeIndicatorThread.kill()
    269                 self.barTimeIndicatorThread = None
    270 
    271     def _UpdateBarTimeIndicator(self):
    272         while self and not self.destroyed:
    273             indicatorValue = float(blue.os.GetWallclockTime() - self.pin.installTime) / (self.pin.expiryTime - self.pin.installTime)
    274             if indicatorValue > 0.0 and indicatorValue < 1.0:
    275                 self.barGraph.ShowTimeIndicator(indicatorValue)
    276             blue.pyos.synchro.SleepWallclock(10000)
    277 
    278     def OnHeadEntryMouseEnter(self, headID):
    279         if not uicore.uilib.leftbtn:
    280             self.headButtons[headID].ShowFill()
    281 
    282     def OnHeadEntryMouseExit(self, headID):
    283         self.headButtons[headID].HideFill()
    284 
    285     def _ConstructRightContainer(self):
    286         self.ConstructResourceSelectionIcons()
    287         if self.pin.IsActive():
    288             self.ConstructCurrCycleGauge()
    289         else:
    290             self.ConstructAreaSliderAndButtons()
    291         self.ConstructOutputLabels()
    292         self.ConstructSubmitButton()
    293         self.ConstructStateIndicatorCont()
    294 
    295     def ConstructCurrCycleGauge(self):
    296         self.currCycleGauge = Gauge(parent=self.rightCont, align=uiconst.TOTOP, value=0.0, color=planetCommonUI.PLANET_COLOR_CYCLE, label=localization.GetByLabel('UI/PI/Common/CurrentCycle'), padTop=10)
    297         uthread.new(self._UpdateCurrCycleGauge)
    298 
    299     def ConstructOutputLabels(self):
    300         self.outputTxtCont = ContainerAutoSize(parent=self.rightCont, align=uiconst.TOTOP, height=40, padTop=10)
    301         CaptionLabel(parent=self.outputTxtCont, text=localization.GetByLabel('UI/PI/Common/Output'), align=uiconst.TOTOP)
    302         self.outputPerHourTxt = EveLabelSmall(parent=self.outputTxtCont, text=localization.GetByLabel('UI/Common/None'), align=uiconst.TOTOP)
    303         self.outputTotalTxt = EveLabelSmall(parent=self.outputTxtCont, align=uiconst.TOTOP)
    304         self.UpdateOutputTextCont()
    305 
    306     def UpdateOutputTextCont(self):
    307         if self.currResourceTypeID:
    308             self.outputTxtCont.Show()
    309         else:
    310             self.outputTxtCont.Hide()
    311 
    312     def ConstructStateIndicatorCont(self):
    313         stateParent = ContainerAutoSize(parent=self.rightCont, align=uiconst.TOBOTTOM, padBottom=2)
    314         self.stateTxt = eveLabel.EveLabelSmall(parent=stateParent, align=uiconst.CENTERTOP, color=Color.WHITE, state=uiconst.UI_NORMAL, padding=(0, 2, 0, 2), width=self.leftCont.width - 10)
    315         self.stateColorFill = Fill(bgParent=stateParent)
    316         self.UpdateStateInfoCont()
    317 
    318     def ConstructResourceSelectionIcons(self):
    319         CaptionLabel(parent=self.rightCont, text=localization.GetByLabel('UI/PI/Common/SelectedResource'), align=uiconst.TOTOP)
    320         self.resourceIconButtons = {}
    321         numIcons = len(self.resourceTypeIDs)
    322         ICONSIZE = 25
    323         PAD = 4
    324         if self.editsEnabled:
    325             state = uiconst.UI_PICKCHILDREN
    326         else:
    327             state = uiconst.UI_DISABLED
    328         iconCont = Container(parent=self.rightCont, align=uiconst.TOTOP, pos=(0,
    329          0,
    330          numIcons * (ICONSIZE + PAD),
    331          ICONSIZE), state=state, padding=(0, 2, 0, 2))
    332         resourceTypes = [ (evetypes.GetName(typeID), typeID) for typeID in self.resourceTypeIDs ]
    333         resourceTypes.sort()
    334         for i, (typeName, typeID) in enumerate(resourceTypes):
    335             ib = IconButton(parent=iconCont, pos=(i * (ICONSIZE + PAD),
    336              0,
    337              ICONSIZE,
    338              ICONSIZE), size=ICONSIZE, typeID=typeID, ignoreSize=True)
    339             self.resourceIconButtons[typeID] = ib
    340             ib.OnClick = (self.OnResourceBtnClicked, typeID)
    341             ib.OnMouseEnter = (self.OnResourceBtnMouseEnter, ib, typeID)
    342             ib.OnMouseExit = (self.OnResourceBtnMouseExit, ib)
    343 
    344         self.selectedResourceTxt = EveLabelSmall(parent=self.rightCont, text='', align=uiconst.TOTOP)
    345         self.SetCurrentResourceText(self.currResourceTypeID)
    346 
    347     def _UpdateCurrCycleGauge(self):
    348         while not self.destroyed:
    349             if self.pin.IsActive():
    350                 self.currCycleGauge.Show()
    351                 txt, cycleProportion = planetCommonUI.GetPinCycleInfo(self.pin, self.programCycleTime)
    352                 self.currCycleGauge.SetValueInstantly(cycleProportion)
    353                 self.currCycleGauge.SetSubText(txt)
    354             else:
    355                 self.currCycleGauge.Hide()
    356             blue.pyos.synchro.SleepWallclock(1000)
    357 
    358     def UpdateStateInfoCont(self):
    359         if self.pin.IsInEditMode():
    360             color = Color.WHITE
    361             text = localization.GetByLabel('UI/PI/Common/EditsPending')
    362             animations.FadeTo(self.stateColorFill, 0.0, 0.5, duration=2.0, curveType=uiconst.ANIM_WAVE, loops=uiconst.ANIM_REPEAT)
    363         elif self.pin.IsActive():
    364             color = Color('GREEN').SetAlpha(0.4).GetRGBA()
    365             text = localization.GetByLabel('UI/PI/Common/SurveyProgramRunning')
    366             animations.FadeTo(self.stateColorFill, 0.2, 0.5, duration=3.0, curveType=uiconst.ANIM_WAVE, loops=uiconst.ANIM_REPEAT)
    367         else:
    368             color = Color.WHITE
    369             text = localization.GetByLabel('UI/Common/Idle')
    370             animations.FadeTo(self.stateColorFill, 0.0, 0.5, duration=2.0, curveType=uiconst.ANIM_WAVE, loops=uiconst.ANIM_REPEAT)
    371         self.stateColorFill.SetRGBA(*color)
    372         self.stateTxt.SetText('<center>' + text)
    373 
    374     def ConstructSubmitButton(self):
    375         if self.editsEnabled:
    376             btnName = localization.GetByLabel('UI/PI/Common/SurveyInstallProgram')
    377             func = self._ApplyProgram
    378         else:
    379             btnName = localization.GetByLabel('UI/PI/Common/SurveyStopProgram')
    380             func = self._StopProgram
    381         self.submitButton = Button(parent=self.rightCont, label=btnName, func=func, align=uiconst.TOBOTTOM, idx=0, fixedheight=20)
    382 
    383     def UpdateSubmitButton(self):
    384         nextEditTime = self.pin.GetNextEditTime()
    385         if nextEditTime is not None and nextEditTime > blue.os.GetWallclockTime() and not self.pin.IsInEditMode():
    386             self.submitButton.Disable()
    387             uthread.new(self._UnlockStopButtonThread).context = '_SetSubmitButtonState'
    388         elif self.currentResource is None or not self.pin.GetNumHeads():
    389             self.submitButton.Disable()
    390             self.submitButton.SetHint('')
    391         else:
    392             self.submitButton.Enable()
    393             self.submitButton.SetHint('')
    394 
    395     def _UnlockStopButtonThread(self):
    396         nextEditTime = self.pin.GetNextEditTime()
    397         while nextEditTime > blue.os.GetWallclockTime():
    398             self.submitButton.SetHint(localization.GetByLabel('UI/PI/Common/SurveyEditsAvailableIn', time=nextEditTime - blue.os.GetWallclockTime()))
    399             blue.pyos.synchro.SleepWallclock(200)
    400             if not self or self.destroyed:
    401                 return
    402 
    403         self.UpdateSubmitButton()
    404 
    405     def OnHeadBtnClicked(self, headID):
    406         head = self.pin.FindHead(headID)
    407         if not head:
    408             self.PlaceProbeAtDefaultPosition(headID)
    409         else:
    410             self.planetUISvc.myPinManager.RemoveExtractionHead(self.pin.id, headID)
    411             if headID in self.extractionHeadValues:
    412                 self.extractionHeadValues.pop(headID)
    413             self.headButtons[headID].SetValue(None)
    414         self.UpdateProgram()
    415         self.UpdateNoExtractorsLabel()
    416         self.UpdateSubmitButton()
    417 
    418     def OnBeginDragExtractionHead(self, headID, surfacePoint):
    419         sm.GetService('audio').SendUIEvent('msg_pi_extractor_play')
    420         sm.GetService('audio').SendUIEvent('msg_pi_extractor_distorted_play')
    421         if self.currResourceTypeID is not None:
    422             self.dragThread = uthread.new(self._WhileExtractionHeadDraggedThread, headID, surfacePoint)
    423         else:
    424             self.headButtons[headID].SetValue(0.0)
    425 
    426     def OnEndDragExtractionHead(self):
    427         sm.GetService('audio').SendUIEvent('msg_pi_extractor_stop')
    428         sm.GetService('audio').SendUIEvent('msg_pi_extractor_distorted_stop')
    429         if self.dragThread is not None:
    430             self.dragThread.kill()
    431             self.dragThread = None
    432 
    433     def OnExtractionHeadMoved(self, headID, surfacePoint):
    434         self.UpdateHeadPosition(headID, surfacePoint)
    435 
    436     def OnExtractionHeadAdded(self, headID, surfacePoint):
    437         self.UpdateProgram()
    438         self.UpdateNoExtractorsLabel()
    439         self._UpdateExtractorValues()
    440 
    441     def _UpdateExtractorValues(self):
    442         for i in xrange(planetCommon.ECU_MAX_HEADS):
    443             value = self.GetResourceLayerValueForHead(i)
    444             self.headButtons[i].SetValue(value)
    445 
    446     def OnExtractionHeadRemoved(self, headID):
    447         self.UpdateProgram()
    448         self.UpdateNoExtractorsLabel()
    449         self._UpdateExtractorValues()
    450 
    451     def _WhileExtractionHeadDraggedThread(self, headID, surfacePoint):
    452         while not self.destroyed:
    453             if self.currentResource is not None:
    454                 self.UpdateProgram(replaceHeadID=headID, point=surfacePoint)
    455             blue.pyos.synchro.SleepWallclock(200)
    456 
    457     def UpdateHeadPosition(self, headID, surfacePoint):
    458         heads = self.pin.heads
    459         headRadius = self.pin.headRadius
    460         if self.currentResource is not None:
    461             value = self.GetResourceLayerValueForHead(headID, surfacePoint)
    462             overlapValue = self.overlapValues.get(headID, None)
    463             self.headButtons[headID].SetValue(value=value, overlapValue=overlapValue)
    464             if value is not None:
    465                 sm.GetService('audio').SetGlobalRTPC('pitch_extractor_quality', 100 * value / 250)
    466             if overlapValue is not None:
    467                 sm.GetService('audio').SetGlobalRTPC('volume_extractor_interference', 10 * overlapValue)
    468         else:
    469             self.headButtons[headID].SetValue(value=0.0)
    470 
    471     def UpdateHeadButtonValues(self):
    472         for headButton in self.headButtons:
    473             value = self.GetResourceLayerValueForHead(headButton.headID)
    474             self.extractionHeadValues[headButton.headID] = value
    475             headButton.SetValue(value)
    476 
    477     def GetResourceLayerValueForHead(self, headID, surfacePoint = None):
    478         if self.currResourceTypeID:
    479             head = self.pin.FindHead(headID)
    480             if head:
    481                 headID, phi, theta = head
    482                 if surfacePoint:
    483                     theta = 2.0 * math.pi - surfacePoint.theta
    484                     phi = surfacePoint.phi
    485                 else:
    486                     theta = 2.0 * math.pi - theta
    487                     phi = phi
    488                 return max(0.0, builder.GetValueAt(self.sh, theta, phi))
    489         else:
    490             if self.pin.FindHead(headID) is not None:
    491                 return 0.0
    492             return
    493 
    494     def PlaceProbeAtDefaultPosition(self, headID):
    495         OFFSET = 0.08
    496         VEC_X = (-1, 0, 0)
    497         rotAngle = float(headID) / planetCommon.ECU_MAX_HEADS * 2 * math.pi
    498         ecuVector = self.planetUISvc.myPinManager.pinsByID[self.pin.id].surfacePoint.GetAsXYZTuple()
    499         normal = geo2.Vec3Cross(ecuVector, VEC_X)
    500         normal = geo2.Vector(*normal) * OFFSET
    501         posVec = geo2.Vec3Subtract(ecuVector, normal)
    502         posVec = geo2.Vec3Normalize(posVec)
    503         rotMat = geo2.MatrixRotationAxis(ecuVector, rotAngle)
    504         posVec = geo2.Multiply(rotMat, posVec)
    505         surfacePoint = planetCommon.SurfacePoint(*posVec)
    506         self.planetUISvc.myPinManager.PlaceExtractionHead(self.pin.id, surfacePoint, headID)
    507         self.UpdateHeadPosition(headID, surfacePoint)
    508 
    509     def _ApplyProgram(self, *args):
    510         if self.currResourceTypeID is not None:
    511             self.planet.InstallProgram(self.pin.id, self.currResourceTypeID, self.currentRadius)
    512         self.planetUISvc.myPinManager.ReRenderPin(self.pin)
    513         self.planetUISvc.eventManager.SelectPin(self.pin.id)
    514         self.Close()
    515 
    516     def _Cancel(self, *args):
    517         self.planetUISvc.CancelInstallProgram(self.pin.id, self.pinData)
    518         self.Close()
    519 
    520     def _StopProgram(self, *args):
    521         self.editsEnabled = True
    522         self.pin.inEditMode = True
    523         self.leftCont.Flush()
    524         self.rightCont.Flush()
    525         self.planetUISvc.myPinManager.UnlockHeads(self.pin.id)
    526         self.planetUISvc.GetCurrentPlanet().InstallProgram(self.pin.id, None, self.currentRadius)
    527         self._ConstructLeftContainer()
    528         self._ConstructRightContainer()
    529         self.SetCurrentResource(self.currResourceTypeID)
    530         self.UpdateSubmitButton()
    531 
    532     def OnResourceBtnClicked(self, typeID, *args):
    533         self.SetCurrentResource(typeID)
    534 
    535     def OnResourceBtnMouseEnter(self, btn, typeID, *args):
    536         self.SetCurrentResourceText(typeID)
    537         IconButton.OnMouseEnter(btn)
    538 
    539     def OnResourceBtnMouseExit(self, btn, *args):
    540         self.SetCurrentResourceText(self.currResourceTypeID)
    541         IconButton.OnMouseExit(btn)
    542 
    543     def SetCurrentResource(self, typeID = None):
    544         self.currResourceTypeID = typeID
    545         self.currentResource = None if typeID is None else self.planet.GetResourceHarmonic(typeID)
    546         self.UpdateSelectedResourceIconBtns(typeID)
    547         self.UpdateSubmitButton()
    548         if typeID is None:
    549             self.sh = None
    550         else:
    551             inRange, self.sh = self.planetUISvc.planet.GetResourceData(self.currResourceTypeID)
    552         self.SetCurrentResourceText(typeID)
    553         self.UpdateHeadButtonValues()
    554         self.UpdateProgram()
    555         self.planetUISvc.ShowResource(typeID)
    556         self.UpdateOutputTextCont()
    557 
    558     def UpdateSelectedResourceIconBtns(self, typeID):
    559         for ibTypeID, ib in self.resourceIconButtons.iteritems():
    560             if self.currResourceTypeID:
    561                 ib.SetDeselected()
    562             if not self.editsEnabled and ibTypeID != typeID:
    563                 ib.Disable()
    564 
    565         if typeID:
    566             ib = self.resourceIconButtons[typeID]
    567             ib.SetSelected()
    568 
    569     def SetCurrentResourceText(self, typeID):
    570         if typeID:
    571             text = evetypes.GetName(typeID)
    572         else:
    573             text = '<color=red>%s</color>' % localization.GetByLabel('UI/PI/Common/NoResourceSelected')
    574         self.selectedResourceTxt.text = text
    575 
    576     def OnPlanetViewChanged(self, newPlanet, oldPlanet):
    577         self.Close()
    578 
    579     def OnEditModeBuiltOrDestroyed(self, planetID):
    580         if not self or self.destroyed:
    581             return
    582         if self.planet.planetID != planetID:
    583             return
    584         if self.planet.GetPin(self.pin.id) is None:
    585             self.Close()
    586 
    587     def OnEditModeChanged(self, isEdit):
    588         if not self or self.destroyed:
    589             return
    590         if not isEdit:
    591             self.Close()
    592 
    593     def CloseByUser(self, *args):
    594         self.planetUISvc.CancelInstallProgram(self.pin.id, self.pinData)
    595         Window.CloseByUser(self, *args)
    596 
    597     def _OnClose(self, *args):
    598         if hasattr(self, 'revertToResource'):
    599             self.planetUISvc.ShowResource(self.revertToResource)
    600         self.planetUISvc.ExitSurveyMode()
    601 
    602     def GetColony(self):
    603         return self.planetUISvc.GetCurrentPlanet().GetColony(session.charid)
    604 
    605 
    606 class ExtractionHeadEntry(Container):
    607     __guid__ = 'uicls.ExtractionHeadEntry'
    608     default_name = 'ExtractionHeadEntry'
    609     default_height = 20
    610     default_state = uiconst.UI_NORMAL
    611     default_padBottom = 9
    612 
    613     def ApplyAttributes(self, attributes):
    614         Container.ApplyAttributes(self, attributes)
    615         self.headID = attributes.headID
    616         self.ecuID = attributes.ecuID
    617         self.value = attributes.get('value', None)
    618         self.pin = attributes.pin
    619         self.overlapValue = None
    620         self.fill = Fill(parent=self, align=uiconst.TOTOP, height=self.default_height, color=Color(*Color.WHITE).SetAlpha(0.1).GetRGBA(), state=uiconst.UI_HIDDEN, idx=0)
    621         self.icon = eveIcon.Icon(parent=self, icon='ui_77_32_38', size=self.default_height, ignoreSize=True, state=uiconst.UI_DISABLED, left=-2)
    622         self.label = EveLabelSmall(parent=self, text='', left=self.default_height, top=4)
    623         self.SetValue(self.value)
    624 
    625     def SetValue(self, value = None, overlapValue = None):
    626         self.value = value
    627         self.overlapValue = overlapValue
    628         if value is None:
    629             txt = localization.GetByLabel('UI/PI/Common/SurveyDashSign')
    630             self.opacity = 0.5
    631         else:
    632             if overlapValue:
    633                 txt = localization.GetByLabel('UI/PI/Common/SurveyHeadValueDisturbed', value=value, percentage=int(math.ceil(100 * overlapValue)))
    634             else:
    635                 txt = localization.GetByLabel('UI/PI/Common/SurveyHeadValue', value=value)
    636             self.opacity = 1.0
    637         self.label.text = txt
    638 
    639     def GetHint(self, *args):
    640         if self.value is None:
    641             return localization.GetByLabel('UI/PI/Common/SurveyInstallHeadHint', power=self.pin.GetExtractorHeadPowerUsage(), cpu=self.pin.GetExtractorHeadCpuUsage())
    642         if self.overlapValue:
    643             disturbanceText = localization.GetByLabel('UI/PI/Common/SurveyExtractorHeadDisturbanceHint', percentage=int(math.ceil(100 * self.overlapValue)))
    644         else:
    645             disturbanceText = ''
    646         hint = localization.GetByLabel('UI/PI/Common/SurveyExtractorHeadHint', headID=self.headID + 1, value=self.value, disturbanceText=disturbanceText)
    647         return hint
    648 
    649     def LoadTooltipPanel(self, tooltipPanel, *args):
    650         tooltipPanel.LoadGeneric2ColumnTemplate()
    651         tooltipPanel.AddLabelMedium(text=self.GetHint(), colSpan=2, cellPadding=(0, 0, 0, 10), wrapWidth=200)
    652         tooltipPanel.AddLabelShortcut(GetByLabel('UI/PI/Common/InstallExtractor'), GetByLabel('UI/PI/Common/InstallExtractorShortcut'))
    653         tooltipPanel.AddSpacer(height=1)
    654         tooltipPanel.AddLabelShortcut(GetByLabel('UI/PI/Common/MoveExtractor'), GetByLabel('UI/PI/Common/MoveExtractorShortcut'))
    655 
    656     def GetTooltipPointer(self):
    657         if self.headID < 5:
    658             return uiconst.POINT_RIGHT_2
    659         else:
    660             return uiconst.POINT_LEFT_2
    661 
    662     def SetOverlapValue(self, overlapValue):
    663         self.SetValue(self.value, overlapValue)
    664 
    665     def OnMouseEnter(self, *args):
    666         if not self or self.destroyed:
    667             return
    668         self.ShowFill()
    669         if self.value is None:
    670             self.label.text = localization.GetByLabel('UI/PI/Common/SurveyInstall')
    671             self.opacity = 1.0
    672         sm.GetService('planetUI').myPinManager.OnExtractionHeadMouseEnter(self.ecuID, self.headID)
    673 
    674     def OnMouseExit(self, *args):
    675         if not self or self.destroyed:
    676             return
    677         self.HideFill()
    678         if self.value is None:
    679             self.label.text = localization.GetByLabel('UI/PI/Common/SurveyDashSign')
    680             self.opacity = 0.5
    681         sm.GetService('planetUI').myPinManager.OnExtractionHeadMouseExit(self.ecuID, self.headID)
    682 
    683     def ShowFill(self):
    684         if self.overlapValue:
    685             self.fill.height = 28
    686         else:
    687             self.fill.height = self.default_height
    688         self.fill.state = uiconst.UI_DISABLED
    689 
    690     def HideFill(self):
    691         self.fill.state = uiconst.UI_HIDDEN