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

skillQueuePanelNew.py (45481B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\skillPlan\skillQueuePanel\skillQueuePanelNew.py
      2 import blue
      3 import clonegrade
      4 import eve.client.script.ui.shared.pointerTool.pointerToolConst as pConst
      5 import evetypes
      6 import expertSystems.client
      7 import gametime
      8 import inventorycommon.const as invconst
      9 import localization
     10 import telemetry
     11 import textImporting.importSkillplanConst as importConst
     12 import uthread
     13 import uthread2
     14 from carbon.client.script.environment.AudioUtil import PlaySound
     15 from carbon.common.script.util.commonutils import GetAttrs
     16 from carbonui import uiconst
     17 from carbonui.text.styles import TextDetail
     18 from carbonui.control.button import Button
     19 from carbonui.control.dragdrop import dragdata
     20 from carbonui.control.scroll import Scroll
     21 from carbonui.primitives.container import Container
     22 from carbonui.primitives.containerAutoSize import ContainerAutoSize
     23 from carbonui.primitives.line import Line
     24 from carbonui.uianimations import animations
     25 from carbonui.uicore import uicore
     26 from carbonui.util.color import Color
     27 from carbonui.util.various_unsorted import GetClipboardData
     28 from characterskills import SKILLQUEUE_MAX_NUM_SKILLS
     29 from characterskills.util import GetSPForLevelRaw
     30 from eve.client.script.ui import eveColor
     31 from eve.client.script.ui.control.entries.util import GetFromClass
     32 from eve.client.script.ui.control.eveLabel import EveLabelSmall, EveLabelLarge, EveCaptionLarge
     33 from eve.client.script.ui.control.utilMenu import UtilMenu
     34 from eve.client.script.ui.shared.cloneGrade import cloneStateUtil
     35 from eve.client.script.ui.shared.monetization.multiTrainingOverlay import MultiTrainingOverlay
     36 from eve.client.script.ui.shared.neocom.skillConst import COLOR_SKILL_2, COLOR_TRAININGALLOWED, COLOR_TRAININGNOTALLOWED, COLOR_MOVE_INDICATOR
     37 from eve.client.script.ui.shared.neocom.timelineContainer import TimelineContainer
     38 from eve.client.script.ui.skillPlan import skillPlanUISignals
     39 from eve.client.script.ui.skillPlan.skillPlanEditor.skillPlanDropIndicatorCont import SkillQueueDropIndicatorCont, IsMultiSkillEntryMove
     40 from eve.client.script.ui.skillPlan.skillQueuePanel.skillImportingMsg import SkillImportStatusWindow
     41 from eve.client.script.ui.skillPlan.skillQueuePanel.skillPurchaseOverlay import show_skill_purchase_overlay
     42 from eve.client.script.ui.skillPlan.skillQueuePanel.skillQueueButtons import ExpertSystemIconButton, ApplySkillPointsButtonTooltip, BUTTON_SIZE, ICON_SIZE, SkillInjectorTooltip, AlphaInjectorTooltip, OmegaTrainingSpeedButton
     43 from eve.client.script.ui.skillPlan.skillQueuePanel.skillQueueLastDropEntry import SkillQueueLastDropEntry
     44 from eve.client.script.ui.skillPlan.skillQueuePanel.skillQueueSkillEntry import SkillQueueSkillEntry
     45 from eve.client.script.ui.skilltrading.applySkillPointsWindow import ApplySkillPointsWindow
     46 from eve.common.lib import appConst
     47 from eveexceptions import UserError
     48 from eveui import clipboard
     49 from localization import GetByLabel
     50 from omega.client.clone_grade_svc import get_clone_grade_svc
     51 from player_messaging.client.ui_message import prompt_player_fsd_dialog
     52 from textImporting.importSkillplan import SkillPlanImportingStatus
     53 ACTION_ICON = 'res:/UI/Texture/classes/UtilMenu/BulletIcon.png'
     54 QUEUEBAR_HEIGHT = 30
     55 COLOR_BG = (0.2, 0.2, 0.2, 0.5)
     56 FILTER_ALL = 0
     57 FILTER_PARTIAL = 1
     58 FILTER_EXCLUDELVL5 = 2
     59 FITSINQUEUE_DEFAULT = 0
     60 NORMAL_COLORS = [(1, 1, 1, 0.4), (1, 1, 1, 0.8)]
     61 
     62 def ShowMultiTrainingMessageOrRaise(e):
     63     if not MultiTrainingOverlay.IsSuppressed():
     64         skillPlanUISignals.on_show_multi_training_msg()
     65         sm.ScatterEvent('OnShowMultiTrainingMessage')
     66     else:
     67         raise e
     68 
     69 
     70 class SkillQueuePanelNew(Container):
     71     __notifyevents__ = ['OnSkillsChanged',
     72      'OnSkillQueueChanged',
     73      'OnSkillQueuePaused',
     74      'OnSkillQueueRefreshed',
     75      'OnFreeSkillPointsChanged_Local',
     76      'OnSubscriptionChanged',
     77      'OnSkillQueueModified']
     78 
     79     def ApplyAttributes(self, attributes):
     80         Container.ApplyAttributes(self, attributes)
     81         self.queueLastApplied = []
     82         self.isSaving = False
     83         self.isApplying = False
     84         self.skillTimer = None
     85         self.timelineCont = None
     86         self.updateThread = None
     87         self._isSkillPurchaseOverlayActive = False
     88         self.godma = sm.GetService('godma')
     89         self.skills = sm.GetService('skills')
     90         self.skillQueueSvc = sm.GetService('skillqueue')
     91         self.ConstructLayout()
     92         self.InitializeData()
     93         self.ReloadUpdateThread()
     94         sm.RegisterNotify(self)
     95 
     96     @telemetry.ZONE_METHOD
     97     def InitializeData(self, *args):
     98         self.UpdateTimeData()
     99         self.skillQueueSvc.BeginTransaction()
    100         self.queueLastApplied = self.skillQueueSvc.GetQueue()
    101         uthread.new(self.LoadQueue)
    102         self.UpdateFreeSkillPoints()
    103         self.UpdateApplySkillPointsButton()
    104 
    105     @telemetry.ZONE_METHOD
    106     def ConstructLayout(self):
    107         self.ConstructTopCont()
    108         self.ConstructSkillQueueBar()
    109         self.ConstructBottomCont()
    110         self.ConstructUnallocatedBanner()
    111         self.ConstructScroll()
    112 
    113     def ConstructUnallocatedBanner(self):
    114         self.unallocatedTextBanner = Container(name='unallocatedTextBanner', parent=self, align=uiconst.TOBOTTOM, height=40, bgColor=Color(*eveColor.SMOKE_BLUE).SetOpacity(0.2).GetRGBA())
    115         self.unallocatedText = EveLabelLarge(parent=self.unallocatedTextBanner, align=uiconst.CENTER)
    116 
    117     def ConstructBottomCont(self):
    118         self.bottomCont = Container(name='bottomCont', parent=self, align=uiconst.TOBOTTOM, height=56, padBottom=8)
    119         textCont = ContainerAutoSize(parent=self.bottomCont, align=uiconst.BOTTOMLEFT, width=300)
    120         self.timeLeftLabel = EveCaptionLarge(name='timeLeftLabel', parent=textCont, align=uiconst.TOBOTTOM, padTop=-2, top=-4)
    121         EveLabelLarge(parent=textCont, align=uiconst.TOBOTTOM, text=GetByLabel('UI/SkillPlan/TrainingTime'))
    122         uthread.new(self._UpdateTrainingTimeLabelThread)
    123         buttonIconCont = ContainerAutoSize(name='ButtonIconCont', parent=self.bottomCont, align=uiconst.BOTTOMRIGHT, height=BUTTON_SIZE)
    124         self._ConstructBottomContButtons(buttonIconCont)
    125         self.UpdatePausePlayButtonTexturePath()
    126 
    127     def _ConstructBottomContButtons(self, buttonIconCont):
    128         padLeft = 8
    129         self.omegaButton = OmegaTrainingSpeedButton(name='omegaButton', parent=buttonIconCont, align=uiconst.TORIGHT, iconSize=32, fixedwidth=60, padLeft=padLeft, texturePath='res:/UI/Texture/classes/Seasons/omega_32x32.png', label='2x')
    130         self.CheckDisplayOmegaButton()
    131         self.applySkillPointsButtonIcon = Button(name='ApplySkillPointsButtonIcon', parent=buttonIconCont, align=uiconst.TORIGHT, func=self.OnApplySkillPointsButton, iconSize=ICON_SIZE, padLeft=padLeft, uniqueUiName=pConst.UNIQUE_NAME_APPLY_SP_BTN)
    132         self.UpdateApplySkillPointsButton()
    133         if expertSystems.is_expert_systems_enabled():
    134             from eve.client.script.ui.shared.neocom.characterSheetWindow import CharacterSheetWindow
    135             ExpertSystemIconButton(parent=buttonIconCont, align=uiconst.TORIGHT, padLeft=padLeft, iconSize=ICON_SIZE, func=CharacterSheetWindow.OpenExpertSystems, texturePath='res:/UI/Texture/classes/ExpertSystems/logo_simple_white_32.png')
    136         self.pausePlayButton = Button(parent=buttonIconCont, align=uiconst.TORIGHT, iconSize=20, func=self.StartOrStopTraining, padLeft=padLeft, uniqueUiName=pConst.UNIQUE_NAME_START_TRAINING_BTN)
    137 
    138     def UpdateApplySkillPointsButton(self):
    139         if self._CanApplySkillPoints():
    140             tooltipPanelClassInfo = ApplySkillPointsButtonTooltip(len(self.skillQueueSvc.GetQueue()), self.skills.GetFreeSkillPoints())
    141             texturePath = 'res:/UI/Texture/classes/SkillPlan/buttonIcons/plusIcon.png'
    142         elif cloneStateUtil.IsOmega():
    143             tooltipPanelClassInfo = SkillInjectorTooltip()
    144             texturePath = 'res:/UI/Texture/classes/skilltrading/marketSkillinjector.png'
    145         else:
    146             tooltipPanelClassInfo = AlphaInjectorTooltip()
    147             texturePath = 'res:/UI/Texture/classes/skilltrading/alphaSkillInjector.png'
    148         self.applySkillPointsButtonIcon.tooltipPanelClassInfo = tooltipPanelClassInfo
    149         self.applySkillPointsButtonIcon.texturePath = texturePath
    150 
    151     def OnApplySkillPointsButton(self, *args):
    152         if self._CanApplySkillPoints():
    153             ApplySkillPointsWindow.Open()
    154         elif cloneStateUtil.IsOmega():
    155             self.ShowInjectorInMarket()
    156         else:
    157             self.BuyAlphaInjector()
    158 
    159     def _CanApplySkillPoints(self):
    160         return len(self.skillQueueSvc.GetQueue()) > 0 and self.skills.GetFreeSkillPoints()
    161 
    162     def UpdatePausePlayButtonTexturePath(self):
    163         if self.skillQueueSvc.SkillInTraining():
    164             texturePath = 'res:/UI/Texture/classes/SkillPlan/buttonIcons/pauseIcon.png'
    165             hint = GetByLabel('UI/SkillPlan/PauseTraining')
    166         else:
    167             texturePath = 'res:/UI/Texture/classes/SkillPlan/buttonIcons/playIcon.png'
    168             hint = GetByLabel('UI/SkillPlan/ResumeTraining')
    169         self.pausePlayButton.texturePath = texturePath
    170         self.pausePlayButton.SetHint(hint)
    171 
    172     def _UpdateTrainingTimeLabelThread(self):
    173         while not self.destroyed:
    174             timeLeft, spLeft = self.skillQueueSvc.GetTrainingLengthOfQueue()
    175             if self.skillQueueSvc.SkillInTraining():
    176                 self.timeLeftLabel.SetRGBA(*eveColor.WHITE)
    177                 self.timeLeftLabel.text = self.GetTimeLeftText(timeLeft)
    178                 self.timeLeftLabel.text = self.GetTimeLeftText(timeLeft)
    179                 self.spLeftLabel.text = GetByLabel('UI/SkillPlan/SpLeftInQueue', spLeft=spLeft)
    180             elif not self.skillQueueSvc.GetQueue():
    181                 self.timeLeftLabel.SetRGBA(*eveColor.WHITE)
    182                 self.timeLeftLabel.text = '-'
    183                 self.spLeftLabel.text = '-'
    184             else:
    185                 self.timeLeftLabel.SetRGBA(*eveColor.HOT_RED)
    186                 self.timeLeftLabel.text = GetByLabel('UI/SkillPlan/TrainingPaused')
    187                 self.spLeftLabel.text = GetByLabel('UI/SkillPlan/SpLeftInQueue', spLeft=spLeft)
    188             blue.synchro.SleepWallclock(500)
    189 
    190     def GetTimeLeftText(self, timeLeft):
    191         if session.languageID == 'DE':
    192             if timeLeft >= gametime.DAY:
    193                 return localization.formatters.FormatTimeIntervalShortWritten(long(timeLeft), showFrom='day', showTo='hour')
    194             elif timeLeft > gametime.HOUR:
    195                 return localization.formatters.FormatTimeIntervalShortWritten(long(timeLeft), showFrom='hour', showTo='minute')
    196             else:
    197                 return localization.formatters.FormatTimeIntervalShortWritten(long(timeLeft), showFrom='minute', showTo='second')
    198         else:
    199             if timeLeft >= gametime.DAY:
    200                 return localization.formatters.FormatTimeIntervalShortWritten(long(timeLeft), showFrom='day', showTo='minute')
    201             return localization.formatters.FormatTimeIntervalShortWritten(long(timeLeft), showFrom='hour', showTo='second')
    202 
    203     def ShowInjectorInMarket(self):
    204         sm.StartService('marketutils').ShowMarketDetails(invconst.typeSkillInjector, None)
    205         PlaySound(uiconst.SOUND_BUTTON_CLICK)
    206 
    207     def BuyAlphaInjector(self):
    208         sm.GetService('vgsService').OpenStore(typeIds=[invconst.typeAlphaTrainingInjector])
    209 
    210     def ConstructTopCont(self):
    211         self.topCont = Container(name='topCont', parent=self, align=uiconst.TOTOP, height=26)
    212         self.captionLabel = EveCaptionLarge(parent=self.topCont, align=uiconst.CENTERLEFT, state=uiconst.UI_NORMAL, text=GetByLabel('UI/CharacterSheet/CharacterSheetWindow/SkillTabs/TrainingQueue'))
    213         self.captionLabel.LoadTooltipPanel = self.LoadCaptionLabelTooltipPanel
    214         UtilMenu(menuAlign=uiconst.TOPLEFT, parent=self.topCont, align=uiconst.CENTERRIGHT, texturePath='res:/UI/Texture/Icons/77_32_49.png', pos=(0, 0, 24, 24), GetUtilMenu=self.GetUtilMenu, iconSize=24)
    215 
    216     def LoadCaptionLabelTooltipPanel(self, tooltipPanel, *args):
    217         tooltipPanel.LoadStandardSpacing()
    218         tooltipPanel.AddMediumHeader(text=GetByLabel('UI/SkillPlan/SkillQueueCounterHeader'))
    219         tooltipPanel.AddLabelMedium(text=GetByLabel('UI/SkillPlan/SkillQueueCounterDescription', numMax=SKILLQUEUE_MAX_NUM_SKILLS), wrapWidth=300)
    220 
    221     def UpdateCaptionLabelText(self, numSkills):
    222         text = GetByLabel('UI/CharacterSheet/CharacterSheetWindow/SkillTabs/TrainingQueue')
    223         text += ' <color=%s>%s<fontsize=12>/%s</fontsize></color>' % (eveColor.LED_GREY_HEX, numSkills, SKILLQUEUE_MAX_NUM_SKILLS)
    224         self.captionLabel.text = text
    225 
    226     def ConstructScroll(self):
    227         self.dropIndicatorCont = SkillQueueDropIndicatorCont(parent=self, align=uiconst.TOBOTTOM)
    228         self.dropIndicatorCont.OnDragEnter = self.OnDropContDragEnter
    229         self.dropIndicatorCont.OnDropData = self.OnDropContDropData
    230         self.scroll = Scroll(name='skillQueueScroll', parent=self, state=uiconst.UI_PICKCHILDREN, padding=(0, 12, 0, 0))
    231         self.scroll.HideBackground()
    232         self.scroll.sr.content.OnDropData = self.DoDropData
    233         self.scroll.sr.content.OnDragEnter = self.OnContentDragEnter
    234         self.scroll.sr.content.OnDragExit = self.OnContentDragExit
    235         self.scroll.sr.content.RemoveSkillFromQueue = self.RemoveSkillFromQueue
    236         self.scroll.OnDelete = self.RemoveSkillFromQueue
    237         self.changeIndicator = Line(parent=self.scroll, align=uiconst.TOBOTTOM, state=uiconst.UI_DISABLED, color=COLOR_SKILL_2, weight=2, padTop=-2, opacity=0.0, idx=0)
    238 
    239     def ConstructSkillQueueBar(self):
    240         skillQueueBar = Container(name='skillQueueBar', parent=self, align=uiconst.TOBOTTOM, height=12, padBottom=14)
    241         self.timelineCont = TimelineContainer(name='barCont', parent=skillQueueBar, align=uiconst.TOTOP, state=uiconst.UI_NORMAL, height=12, bgColor=COLOR_BG, barHeight=12)
    242         self.timelineCont.OnMouseEnter = self.OnTimelineContMouseEnter
    243         self.timelineCont.OnMouseExit = self.OnTimelineContMouseExit
    244         self.belowBarCont = Container(name='belowBarCont', parent=skillQueueBar, align=uiconst.TOTOP, top=4, height=20, clipChildren=True)
    245         self.spLeftLabel = TextDetail(parent=self.belowBarCont, align=uiconst.TOTOP_NOPUSH, text='', top=1)
    246         self.timelineTickCont = Container(name='timelineTickCont', parent=self.belowBarCont, align=uiconst.TOTOP, height=20, opacity=0.0, top=1)
    247 
    248     def OnTimelineContMouseEnter(self, *args):
    249         animations.FadeIn(self.timelineTickCont)
    250         animations.FadeOut(self.spLeftLabel)
    251         self.DrawBars(animate=True)
    252 
    253     def OnTimelineContMouseExit(self):
    254         animations.FadeOut(self.timelineTickCont)
    255         animations.FadeIn(self.spLeftLabel, endVal=0.75)
    256         self.DrawBars(animate=True)
    257 
    258     def GetUtilMenu(self, menuParent):
    259         menuParent.AddIconEntry(icon='res:/UI/Texture/classes/UtilMenu/BulletIcon.png', text=GetByLabel('UI/SkillPlan/CopySkillsToClipboard'), callback=self.CopySkillsToClipboard)
    260         hint = GetByLabel('UI/SkillQueue/ImportSkillplanFormatHint', typeID=appConst.typeSpaceshipCommand)
    261         menuParent.AddIconEntry(icon=ACTION_ICON, text=GetByLabel('UI/SkillQueue/AddSkillPlanToEndOfQueue'), hint=hint, callback=self.ImportFromClipboard)
    262         menuParent.AddIconEntry(icon=ACTION_ICON, text=GetByLabel('UI/SkillQueue/ReplaceSkillsWithImportedPlan'), hint=hint, callback=self.ImportFromClipboardReplace)
    263         menuParent.AddIconEntry(icon=ACTION_ICON, text=GetByLabel('UI/SkillQueue/ClearSkillQueue'), callback=self.ClearSkillQueue)
    264 
    265     def CopySkillsToClipboard(self):
    266         skillsTxt = ''
    267         for typeID, level in self.skillQueueSvc.GetQueueAsRequirements():
    268             skillsTxt += '%s %s\n' % (evetypes.GetName(typeID), level)
    269 
    270         clipboard.set(skillsTxt)
    271 
    272     def ClearSkillQueue(self):
    273         self.DoRemove(None, self.scroll.GetNodes())
    274 
    275     def GrayButton(self, btn, gray = 1):
    276         inTraining = self.skillQueueSvc.SkillInTraining()
    277         if gray and not inTraining:
    278             btn.SetLabel(['<color=gray>', GetByLabel('UI/Commands/Pause')])
    279             btn.state = uiconst.UI_DISABLED
    280         else:
    281             btn.SetLabel(GetByLabel('UI/Commands/Pause'))
    282             btn.state = uiconst.UI_NORMAL
    283 
    284     def ApplySkillQueueIfNotActive(self, *args):
    285         self.ApplySkillQueue(activate=not self.IsTraining())
    286 
    287     def ApplySkillQueue(self, activate = True):
    288         beginNewTransaction = True
    289         try:
    290             self.SaveChanges(activate=activate)
    291         except UserError as e:
    292             if e.msg == 'UserAlreadyHasSkillInTraining':
    293                 beginNewTransaction = False
    294                 ShowMultiTrainingMessageOrRaise(e)
    295             elif e.msg == 'SkillInQueueRequiresOmegaCloneState':
    296                 beginNewTransaction = False
    297                 raise
    298             elif e.msg == 'SkillInQueueOverAlphaSpTrainingSize':
    299                 message = localization.GetByLabel('UI/SkillQueue/AlphaTrainingLimitReached', limit=clonegrade.CLONE_STATE_ALPHA_MAX_TRAINING_SP)
    300                 sm.ScatterEvent('OnShowAlphaTrainingMessage', message)
    301                 skillPlanUISignals.on_show_alpha_training_msg(message)
    302                 if activate:
    303                     self.ApplySkillQueue(activate=False)
    304                 else:
    305                     beginNewTransaction = False
    306             else:
    307                 raise
    308         finally:
    309             if beginNewTransaction and not self.skillQueueSvc.IsTransactionOpen():
    310                 self.skillQueueSvc.BeginTransaction()
    311 
    312     def SaveChanges(self, activate = True):
    313         if self.isSaving:
    314             return
    315         self.isSaving = True
    316         try:
    317             queue = self.skillQueueSvc.GetQueue()
    318             self.skillQueueSvc.CommitTransaction(activate=activate)
    319             self.queueLastApplied = queue
    320         finally:
    321             self.isSaving = False
    322 
    323     def PauseTraining(self, *args):
    324         inTraining = self.skillQueueSvc.SkillInTraining()
    325         if inTraining:
    326             sm.StartService('skills').AbortTrain()
    327             self.ApplySkillQueue(activate=False)
    328 
    329     def Close(self, *args):
    330         Container.Close(self)
    331         uthread.new(self.SaveQueueOnClose)
    332 
    333     def SaveQueueOnClose(self):
    334         if self.isSaving:
    335             return
    336         queue = {x.queuePosition:(x.trainingTypeID, x.trainingToLevel) for x in self.skillQueueSvc.GetQueue()}
    337         queueLastApplied = {x.queuePosition:(x.trainingTypeID, x.trainingToLevel) for x in self.queueLastApplied}
    338         if queue != queueLastApplied:
    339             isActive = bool(self.skillQueueSvc.SkillInTraining())
    340             self.skillQueueSvc.CommitTransaction(activate=isActive)
    341         else:
    342             self.skillQueueSvc.RollbackTransaction()
    343 
    344     @telemetry.ZONE_METHOD
    345     def LoadQueue(self):
    346         self.queueTimer = 0
    347         mySkills = sm.StartService('skills').GetSkillsIncludingLapsed()
    348         queue = self.skillQueueSvc.GetQueue()
    349         allTrainingLengths = self.skillQueueSvc.GetAllTrainingLengths()
    350         scrolllist = []
    351         for traininigSkill in queue:
    352             skill = mySkills.get(traininigSkill.trainingTypeID, None)
    353             if skill is None:
    354                 self.skillQueueSvc.RemoveSkillFromQueue(traininigSkill.trainingTypeID, traininigSkill.trainingToLevel)
    355                 continue
    356             time = allTrainingLengths.get((traininigSkill.trainingTypeID, traininigSkill.trainingToLevel), [0, 0, 0])
    357             entry = self.GetRightEntry(traininigSkill.trainingTypeID, skill, traininigSkill.trainingToLevel, time[1], time[2])
    358             scrolllist.append(entry)
    359 
    360         if scrolllist:
    361             lastDropEntry = GetFromClass(SkillQueueLastDropEntry)
    362             scrolllist.append(lastDropEntry)
    363         self.scroll.Load(contentList=scrolllist, noContentHint=GetByLabel('UI/SkillQueue/NoSkillsInTraining'))
    364         self.UpdateTime()
    365         self.UpdateAlphaTrainingLimitFrames()
    366         self.UpdateCaptionLabelText(len(queue))
    367 
    368     def UpdateAlphaTrainingLimitFrames(self):
    369         isOmega = get_clone_grade_svc().is_omega_with_grace_period()
    370         nodes = self.scroll.GetNodes()
    371         accumulatedSkillPoints = 0
    372         totalSkillPoints = sm.GetService('skills').GetTotalSkillPointsForCharacterCached()
    373         for node in nodes:
    374             if not node.skill:
    375                 continue
    376             if isOmega:
    377                 if node.panel:
    378                     node.panel.HideDisabledFrame()
    379                 continue
    380             skillPointsForPreviousLevel = GetSPForLevelRaw(node.skill.skillRank, node.trainToLevel - 1)
    381             skillPointsForThisLevel = GetSPForLevelRaw(node.skill.skillRank, node.trainToLevel)
    382             totalSkillPointsInThisLevel = skillPointsForThisLevel - skillPointsForPreviousLevel
    383             currentSkillPoints = self.skillQueueSvc.GetEstimatedSkillPointsTrained(node.skill.typeID)
    384             skillPointsAlreadyTrainedThisLevel = max(currentSkillPoints - skillPointsForPreviousLevel, 0)
    385             addedSkillPoints = max(0, totalSkillPointsInThisLevel - skillPointsAlreadyTrainedThisLevel)
    386             accumulatedSkillPoints += addedSkillPoints
    387             if totalSkillPoints + accumulatedSkillPoints > clonegrade.CLONE_STATE_ALPHA_MAX_TRAINING_SP:
    388                 node.disabledForTraining = True
    389             elif get_clone_grade_svc().get_max_skill_level(node.skill.typeID) < node.trainToLevel:
    390                 node.disabledForTraining = True
    391             else:
    392                 node.disabledForTraining = False
    393             if node.panel:
    394                 if node.disabledForTraining:
    395                     node.panel.ShowDisabledFrame()
    396                 else:
    397                     node.panel.HideDisabledFrame()
    398 
    399     def UpdateActiveSkillFlashAnimation(self):
    400         isTraining = self.skillQueueSvc.SkillInTraining() is not None
    401         nodes = self.scroll.GetNodes()
    402         for i, node in enumerate(nodes):
    403             if not node.panel or isinstance(node.panel, SkillQueueLastDropEntry):
    404                 continue
    405             if i == 0 and isTraining:
    406                 node.panel.StartFlashAnimation()
    407             else:
    408                 node.panel.StopFlashAnimation()
    409 
    410     @telemetry.ZONE_METHOD
    411     def GetRightEntry(self, typeID, skill, trainToLevel, timeLeft, isAccelerated):
    412         skill = self.skills.GetSkillIncludingLapsed(typeID)
    413         isTrained = skill is not None
    414         skillInTraining = self.skillQueueSvc.SkillInTraining()
    415         inTraining = skillInTraining is not None and skillInTraining.typeID == typeID
    416         return GetFromClass(SkillQueueSkillEntry, {'invtype': typeID,
    417          'skill': skill,
    418          'trained': isTrained,
    419          'inQueue': 1,
    420          'trainToLevel': trainToLevel,
    421          'currentLevel': skill.trainedSkillLevel,
    422          'timeLeft': timeLeft,
    423          'skillID': typeID,
    424          'inTraining': [0, 1][inTraining],
    425          'isAccelerated': isAccelerated,
    426          'RemoveFromQueue': self.EntryRemoveFromQueue})
    427 
    428     def EntryRemoveFromQueue(self, x):
    429         self.DoRemove(None, [x])
    430 
    431     def UpdateTime(self):
    432         self.UpdateTimeData()
    433         self.UpdateSkillQueueBar()
    434 
    435     @telemetry.ZONE_METHOD
    436     def UpdateTimeData(self):
    437         self.queueEnds = self.skillQueueSvc.GetTrainingEndTimeOfQueue()
    438         self.queueTimeLeft = self.queueEnds - blue.os.GetWallclockTime()
    439         self.allTrainingLengths = self.skillQueueSvc.GetAllTrainingLengths()
    440         self.lasttime = blue.os.GetWallclockTime()
    441 
    442     def ReloadUpdateThread(self):
    443         if self.updateThread:
    444             self.updateThread.kill()
    445         self.updateThread = uthread.new(self.UpdateThread)
    446 
    447     @telemetry.ZONE_METHOD
    448     def UpdateThread(self, *args):
    449         while True:
    450             blue.synchro.SleepWallclock(1000)
    451             if self.destroyed:
    452                 break
    453             self._Update()
    454 
    455     def _Update(self):
    456         inTraining = self.skillQueueSvc.SkillInTraining()
    457         now = blue.os.GetWallclockTime()
    458         lasttime = self.lasttime or now
    459         diff = now - lasttime
    460         queueEnds = self.queueEnds
    461         timeLeft = self.queueTimeLeft
    462         if queueEnds is not None and queueEnds >= now and timeLeft is not None:
    463             if inTraining and self.skillQueueSvc.FindInQueue(inTraining.typeID, inTraining.trainedSkillLevel + 1) is not None:
    464                 timeLeft = timeLeft - diff
    465                 self.queueTimeLeft = timeLeft
    466             else:
    467                 queueEnds = self.queueEnds + diff
    468                 self.queueEnds = queueEnds
    469             self.lasttime = now
    470             self.UpdateSkillQueueBar()
    471 
    472     def UpdateSkillQueueBar(self):
    473         self.DrawBars()
    474         self.DrawTimelineTicks()
    475 
    476     @telemetry.ZONE_METHOD
    477     def DrawBars(self, animate = False):
    478         self.timelineCont.FlushLine()
    479         offset = 0.0
    480         queue = self.skillQueueSvc.GetQueue()
    481         entries = self.scroll.GetNodes()
    482         unallocatedPoints = sm.GetService('skills').GetFreeSkillPoints()
    483         unallocatedWidth = 0.0
    484         timelineDuration = self.GetQueueDuration()
    485         inTraining = self.skillQueueSvc.SkillInTraining()
    486         actualEnd = self.skillQueueSvc.GetEndOfTraining(inTraining.typeID) if inTraining else None
    487         for i, (skill, entry) in enumerate(zip(queue, entries)):
    488             trainingID = (skill.trainingTypeID, skill.trainingToLevel)
    489             if trainingID not in self.allTrainingLengths:
    490                 continue
    491             timeLeft = self.allTrainingLengths[trainingID][1]
    492             if inTraining and skill.trainingTypeID == inTraining.typeID and skill.trainingToLevel - 1 == inTraining.trainedSkillLevel:
    493                 if actualEnd is not None:
    494                     timeLeft = float(actualEnd - blue.os.GetWallclockTime())
    495             width = timeLeft / timelineDuration
    496             segments = []
    497             if unallocatedPoints > 0:
    498                 pointsTrained = skill.trainingDestinationSP - skill.trainingStartSP
    499                 if unallocatedPoints >= pointsTrained:
    500                     segments.append((width, Color.WHITE))
    501                     unallocatedWidth += width
    502                 else:
    503                     fractionCovered = min(1.0, unallocatedPoints / float(pointsTrained))
    504                     segments.append((width * fractionCovered, Color.WHITE))
    505                     segments.append((width * (1.0 - fractionCovered), NORMAL_COLORS[i % 2]))
    506                     unallocatedWidth += fractionCovered * width
    507                 unallocatedPoints -= pointsTrained
    508             else:
    509                 segments.append((width, NORMAL_COLORS[i % 2]))
    510             try:
    511                 if entry.panel is not None:
    512                     if uicore.uilib.mouseOver == self.timelineCont:
    513                         entry.panel.DrawTimeline(offset, segments, animate)
    514                     else:
    515                         entry.panel.HideTimeline(animate)
    516             except AttributeError:
    517                 pass
    518 
    519             for w, c in segments:
    520                 color = NORMAL_COLORS[i % 2]
    521                 self.timelineCont.AddSegment(w, color)
    522 
    523             offset += width
    524 
    525         self.timelineCont.SetDuration(timelineDuration)
    526 
    527     def DrawTimelineTicks(self):
    528         INTERVALS = [(appConst.HOUR,
    529           6 * appConst.HOUR,
    530           28,
    531           localization.formatters.TIME_CATEGORY_HOUR),
    532          (6 * appConst.HOUR,
    533           appConst.DAY,
    534           29,
    535           localization.formatters.TIME_CATEGORY_DAY),
    536          (appConst.DAY,
    537           appConst.WEEK,
    538           31,
    539           localization.formatters.TIME_CATEGORY_DAY),
    540          (appConst.WEEK,
    541           appConst.MONTH28,
    542           28,
    543           localization.formatters.TIME_CATEGORY_MONTH),
    544          (appConst.MONTH30,
    545           6 * appConst.MONTH30,
    546           25,
    547           localization.formatters.TIME_CATEGORY_MONTH),
    548          (appConst.MONTH30,
    549           appConst.YEAR360,
    550           999,
    551           localization.formatters.TIME_CATEGORY_YEAR)]
    552         for minorTick, majorTick, tickLimit, timeCategory in INTERVALS:
    553             queueDuration = self.GetQueueDuration()
    554             tickCount = queueDuration / float(minorTick)
    555             if tickCount > tickLimit:
    556                 continue
    557             self.timelineTickCont.Flush()
    558             for i in xrange(1, int(tickCount) + 1):
    559                 timeValue = i * minorTick
    560                 isMajorTick = timeValue % majorTick == 0
    561                 if not isMajorTick:
    562                     continue
    563                 self.ConstructTimelineTick(i / tickCount, timeCategory, timeValue)
    564 
    565             break
    566 
    567     def ConstructTimelineTick(self, left, timeCategory, timeValue):
    568         cont = Container(parent=self.timelineTickCont, align=uiconst.TOPLEFT_PROP, left=left, width=1, height=1)
    569         Line(parent=cont, align=uiconst.TOPLEFT, weight=1, top=-1, width=1, height=5, color=COLOR_BG)
    570         text = localization.formatters.FormatTimeIntervalShortWritten(timeValue, showFrom=timeCategory, showTo=timeCategory)
    571         w, _ = EveLabelSmall.MeasureTextSize(text=text)
    572         EveLabelSmall(parent=cont, align=uiconst.RELATIVE, left=-0.5 * w, top=0, text=text, opacity=0.4)
    573 
    574     def GetQueueDuration(self):
    575         return max(self.queueTimeLeft, appConst.DAY + appConst.HOUR)
    576 
    577     def OnFreeSkillPointsChanged_Local(self):
    578         self.UpdateFreeSkillPoints()
    579         self.UpdateApplySkillPointsButton()
    580 
    581     def UpdateFreeSkillPoints(self):
    582         unallocatedPoints = sm.GetService('skills').GetFreeSkillPoints()
    583         if unallocatedPoints > 0:
    584             color = Color.RGBtoHex(*eveColor.CRYO_BLUE)
    585             text = GetByLabel('UI/SkillQueue/FreeSkillPointAmount', points=unallocatedPoints, color=color)
    586             self.unallocatedText.SetText(text)
    587             self.unallocatedTextBanner.display = True
    588         else:
    589             self.unallocatedTextBanner.display = False
    590 
    591     def AddSkillThroughSkillEntry(self, data, queue, idx = -1):
    592         if not data.Get('trained', True):
    593             self.HandleMissingSkill(data.skillID, idx)
    594             return False
    595         nextLevel = self.skillQueueSvc.FindNextLevel(data.skillID, None, queue)
    596         if nextLevel is None or nextLevel > 5:
    597             prompt_player_fsd_dialog('CustomNotify', {'notify': GetByLabel('UI/SkillQueue/SkillFullyPlanned')})
    598             return False
    599         isCloneStateRestricted = get_clone_grade_svc().is_skill_level_restricted(data.skillID, nextLevel)
    600         if isCloneStateRestricted:
    601             prompt_player_fsd_dialog('CustomNotify', {'notify': GetByLabel('UI/SkillQueue/SkillRequiresCloneStateUpgrade')})
    602             return False
    603         self.DoAddSkill(data.invtype, nextLevel, idx)
    604         return True
    605 
    606     def AddSkillsThroughOtherEntry(self, skillID, idx = None, queue = None, nextLevel = None):
    607         mySkills = sm.StartService('skills').GetSkillsIncludingLapsed()
    608         skill = mySkills.get(skillID, None)
    609         if skill is None:
    610             self.HandleMissingSkill(skillID, idx)
    611             return
    612         if nextLevel is None:
    613             if queue is None:
    614                 queue = self.skillQueueSvc.GetQueue()
    615             nextLevel = self.skillQueueSvc.FindNextLevel(skillID, skill.trainedSkillLevel, queue)
    616         if nextLevel > 5:
    617             return
    618         self.DoAddSkill(skillID, nextLevel, idx)
    619         sm.ScatterEvent('OnSkillQueueRefreshed')
    620         return True
    621 
    622     def DoAddSkill(self, typeID, level, position = None):
    623         self.skillQueueSvc.AddSkillToQueue(typeID, level, position=position)
    624 
    625     def HandleMissingSkill(self, typeID, position = None):
    626         if self._isSkillPurchaseOverlayActive:
    627             return
    628         self._isSkillPurchaseOverlayActive = True
    629         controller = show_skill_purchase_overlay(self, typeID, position)
    630         controller.on_closed.connect(self._OnSkillPurchaseOverlayClosed)
    631 
    632     def _OnSkillPurchaseOverlayClosed(self):
    633         self._isSkillPurchaseOverlayActive = False
    634 
    635     def OnSkillQueueModified(self):
    636         self.LoadQueueAndAnimate()
    637 
    638     def LoadQueueAndAnimate(self):
    639         self.ReloadUpdateThread()
    640         animations.FadeTo(self.timelineCont, 1.0, 0.0, duration=0.2, sleep=True)
    641         self.LoadQueue()
    642         duration = 2.0
    643         animations.FadeTo(self.timelineCont, 0.0, 1.0, duration=duration, curveType=uiconst.ANIM_OVERSHOT5)
    644 
    645     def DoMove(self, data, newIdx, queueLength, movingBelow = 0, allEntries = None):
    646         skillPos = newIdx
    647         if newIdx == -1:
    648             skillPos = queueLength - 1
    649         if len(allEntries) == 1:
    650             self._DoMoveOneEntry(data, skillPos)
    651         else:
    652             self._DoMoveManyEntries(skillPos, queueLength, allEntries)
    653 
    654     def _DoMoveOneEntry(self, data, skillPos):
    655         trainToLevel = data.Get('trainToLevel', None)
    656         if data.skillID and trainToLevel:
    657             self.skillQueueSvc.MoveSkillToPosition(data.skillID, trainToLevel, skillPos)
    658 
    659     def _DoMoveManyEntries(self, skillPos, queueLength, allEntries):
    660         dataAbovePoint = [ x for x in allEntries if x.idx < skillPos ]
    661         dataBelowPoint = [ x for x in allEntries if x.idx >= skillPos ]
    662         reverseData = dataAbovePoint[:]
    663         reverseData.reverse()
    664         nextSkillPos = skillPos
    665         for eachData in reverseData:
    666             for testPos in xrange(nextSkillPos, 0, -1):
    667                 if self.skillQueueSvc.CheckCanInsertSkillAtPosition(eachData.skillID, eachData.trainToLevel, testPos, check=True):
    668                     self.skillQueueSvc.MoveSkillToPosition(eachData.skillID, eachData.trainToLevel, testPos)
    669                     if nextSkillPos == testPos:
    670                         nextSkillPos -= 1
    671                     break
    672 
    673         for eachData in dataBelowPoint:
    674             for testPos in xrange(skillPos, queueLength):
    675                 if self.skillQueueSvc.CheckCanInsertSkillAtPosition(eachData.skillID, eachData.trainToLevel, testPos, check=True):
    676                     self.skillQueueSvc.MoveSkillToPosition(eachData.skillID, eachData.trainToLevel, testPos)
    677                     if testPos == skillPos:
    678                         skillPos += 1
    679                     break
    680 
    681         uthread2.call_after_wallclocktime_delay(self.ReselectDraggedNodes, 0.3, allEntries)
    682 
    683     def ReselectDraggedNodes(self, allEntries):
    684         allDragged = {(x.skillID, x.Get('trainToLevel', None)) for x in allEntries}
    685         nodesToSelect = []
    686         for eachNode in self.scroll.GetNodes():
    687             if (eachNode.skillID, eachNode.Get('trainToLevel', None)) in allDragged:
    688                 nodesToSelect.append(eachNode)
    689 
    690         self.scroll.SelectNodes(nodesToSelect)
    691 
    692     def RemoveSkillFromQueue(self, *args):
    693         selected = self.scroll.GetSelected()
    694         self.DoRemove(None, selected)
    695 
    696     def DoRemove(self, dragObj, entries, *args):
    697         entries.reverse()
    698         removeList = []
    699         for entry in entries:
    700             if entry.__guid__ != 'listentry.SkillQueueSkillEntry':
    701                 continue
    702             if not GetAttrs(entry, 'inQueue'):
    703                 return
    704             removeList.append(entry)
    705 
    706         skills = [ (entry.invtype, entry.Get('trainToLevel', -1)) for entry in removeList ]
    707         self.skillQueueSvc.RemoveSkillsFromQueue(skills)
    708         self.LoadQueue()
    709 
    710     def OnContentDragEnter(self, dragObj, entries, *args):
    711         self.ShowPositionIndicator(entries)
    712 
    713     def ShowPositionIndicator(self, entries):
    714         color = self.GetIndicatorColor(entries)
    715         if not self.scroll.IsScrollbarVisible() and self.scroll.GetNodes():
    716             lastDropEntry = self.GetLastDropEntry()
    717             if lastDropEntry:
    718                 lastDropEntry.ShowIndicator(color)
    719         else:
    720             self.changeIndicator.SetRGBA(*color)
    721 
    722     def GetIndicatorColor(self, entries):
    723         if IsMultiSkillEntryMove(entries):
    724             return COLOR_MOVE_INDICATOR
    725         try:
    726             allowedMove = self.skillQueueSvc.IsMoveAllowed(entries[0], None)
    727         except UserError:
    728             allowedMove = False
    729 
    730         if allowedMove:
    731             color = COLOR_TRAININGALLOWED
    732         elif self.skillQueueSvc.IsSkillEntry(entries[0]):
    733             color = COLOR_TRAININGNOTALLOWED
    734         else:
    735             color = (0, 0, 0, 0)
    736         return color
    737 
    738     def GetLastDropEntry(self):
    739         nodes = self.scroll.GetNodes()
    740         if nodes:
    741             return nodes[-1].panel
    742 
    743     def OnContentDragExit(self, *args):
    744         self.HidePositionIndicator()
    745 
    746     def HidePositionIndicator(self):
    747         self.changeIndicator.opacity = 0.0
    748         lastDropEntry = self.GetLastDropEntry()
    749         if lastDropEntry:
    750             lastDropEntry.HideIndicator()
    751 
    752     def OnDropContDragEnter(self, dragObject, entries):
    753         uthread2.StartTasklet(self.dropIndicatorCont.OnDragEnterFromParent, entries, self.scroll)
    754 
    755     def OnDropContDropData(self, dropObj, entries):
    756         if self.scroll.IsScrollbarVisible() and self.scroll.GetScrollProportion() >= 1.0:
    757             self.DoDropData(dropObj, entries)
    758         self.dropIndicatorCont.HideIndicator()
    759 
    760     def DoDropData(self, dragObj, entries, idx = -1):
    761         queue = self.skillQueueSvc.GetQueue()
    762         self.HidePositionIndicator()
    763         if not entries:
    764             return
    765         if idx == -1:
    766             idx = len(queue)
    767         data = entries[0]
    768         if isinstance(data, dragdata.TypeDragData) and evetypes.IsSkill(data.typeID):
    769             self.AddSkillsThroughOtherEntry(data.typeID, idx, queue)
    770         elif data.__guid__ == 'listentry.SkillQueueSkillEntry':
    771             if data.Get('inQueue', None) and not uicore.uilib.Key(uiconst.VK_SHIFT):
    772                 movingBelow = 0
    773                 if idx > data.idx:
    774                     movingBelow = 1
    775                 newIdx = max(0, idx)
    776                 if data.skillID:
    777                     self.DoMove(data, newIdx, len(queue), movingBelow, allEntries=entries)
    778             else:
    779                 self.AddSkillThroughSkillEntry(data, queue, idx)
    780         elif data.__guid__ == 'listentry.SkillEntry':
    781             self.AddSkillThroughSkillEntry(data, queue, idx)
    782         elif data.__guid__ in ('xtriui.InvItem', 'listentry.InvItem'):
    783             category = GetAttrs(data, 'rec', 'categoryID')
    784             if category == appConst.categorySkill:
    785                 sm.StartService('skills').InjectSkillIntoBrain([data.item])
    786                 blue.pyos.synchro.SleepWallclock(500)
    787                 self.AddSkillsThroughOtherEntry(data.item.typeID, idx, queue, nextLevel=1)
    788         elif data.__guid__ == 'listentry.SkillTreeEntry':
    789             self.AddSkillsThroughOtherEntry(data.typeID, idx, queue)
    790         elif data.__guid__ in ('uicls.GenericDraggableForTypeID', 'listentry.GenericMarketItem'):
    791             categoryID = evetypes.GetCategoryID(data.typeID)
    792             if categoryID == appConst.categorySkill:
    793                 self.AddSkillsThroughOtherEntry(data.typeID, idx, queue)
    794 
    795     def UpdateQueuedSkillsRemoveButton(self):
    796         for node in self.scroll.GetNodes():
    797             if node.__guid__ != 'listentry.SkillQueueSkillEntry':
    798                 continue
    799             if node.panel:
    800                 node.panel.UpdateRemoveButton()
    801 
    802     def ChangeTimesOnEntriesInQueue(self):
    803         timesForSkillsInQueue = self.skillQueueSvc.GetAllTrainingLengths()
    804         for node in self.scroll.GetNodes():
    805             if node.__guid__ != 'listentry.SkillQueueSkillEntry':
    806                 continue
    807             times = timesForSkillsInQueue.get((node.skillID, node.trainToLevel))
    808             if times:
    809                 timeLeft, addedTime, isAccelerated = times
    810             else:
    811                 addedTime = 0
    812                 isAccelerated = False
    813             self.ChangeTimeOnSingleEntry(node, timeLeft=addedTime, isAccelerated=isAccelerated)
    814 
    815     def ChangeTimeOnSingleEntry(self, node, timeLeft = 0, isAccelerated = False):
    816         timeHasChanged = long(node.timeLeft) != long(timeLeft)
    817         node.timeLeft = timeLeft
    818         node.isAccelerated = isAccelerated
    819         if node.panel and timeHasChanged:
    820             if timeLeft > 0:
    821                 timeLeftText = localization.formatters.FormatTimeIntervalShortWritten(long(timeLeft), showFrom='day', showTo='second')
    822             else:
    823                 timeLeftText = ''
    824             node.panel.timeLeftText.SetText(timeLeftText)
    825             node.panel.UpdateAcceleratedMarker()
    826             node.panel.FillBoxes(node.skill.trainedSkillLevel, node.trainToLevel)
    827 
    828     def ReloadEntriesIfNeeded(self):
    829         self.ChangeTimesOnEntriesInQueue()
    830         self.UpdateQueuedSkillsRemoveButton()
    831 
    832     def OnDropData(self, *args):
    833         self.DoDropData(*args)
    834 
    835     def OnDragEnter(self, *args):
    836         self.OnContentDragEnter(*args)
    837 
    838     def OnDragExit(self, *args):
    839         self.OnContentDragExit(*args)
    840 
    841     def OnSkillsChanged(self, *args):
    842         self.queueLastApplied = self.skillQueueSvc.GetServerQueue()
    843         self.skillQueueSvc.PrimeCache(force=True)
    844         self.UpdatePausePlayButtonTexturePath()
    845         self.UpdateApplySkillPointsButton()
    846         self.LoadQueueAndAnimate()
    847 
    848     def OnSubscriptionChanged(self, *args):
    849         self.OnSkillsChanged()
    850         self.CheckDisplayOmegaButton()
    851 
    852     def CheckDisplayOmegaButton(self):
    853         self.omegaButton.display = not get_clone_grade_svc().is_omega_with_grace_period()
    854 
    855     def StartOrStopTraining(self, *args):
    856         if self.skillQueueSvc.SkillInTraining():
    857             self.PauseTraining()
    858         else:
    859             self.ApplySkillQueue()
    860             PlaySound('skill_training_start_play')
    861 
    862     def SaveSkillQueue(self):
    863         isTraining = bool(self.skillQueueSvc.SkillInTraining())
    864         self.ApplySkillQueue(activate=isTraining)
    865 
    866     def OnSkillQueueChanged(self):
    867         self.queueLastApplied = self.skillQueueSvc.GetQueue()
    868         self.OnSkillsChanged()
    869 
    870     def OnSkillQueueRefreshed(self):
    871         self.OnSkillQueueChanged()
    872 
    873     def OnSkillQueuePaused(self):
    874         self.OnSkillQueueChanged()
    875 
    876     def ImportFromClipboardReplace(self):
    877         return self.ImportFromClipboard(clearQueue=1)
    878 
    879     def ImportFromClipboard(self, clearQueue = 0, *args):
    880         sm.GetService('loading').ProgressWnd(GetByLabel('UI/SkillQueue/ImportingSkillPlan'), '', 1, 6)
    881         uthread.new(self.ImportFromClipboard_thread, clearQueue)
    882 
    883     def ImportFromClipboard_thread(self, clearQueue = 0, *args):
    884         importingText = GetByLabel('UI/SkillQueue/ImportingSkillPlan')
    885         skillplanImporter = self.skillQueueSvc.GetSkillPlanImporter()
    886         sm.GetService('loading').ProgressWnd(importingText, '', 2, 6)
    887         skillPlanText = GetClipboardData()
    888         skillList, failedLines = skillplanImporter.GetSkillsToAdd(skillPlanText, clearQueue)
    889         if clearQueue:
    890             nodes = self.scroll.GetNodes()
    891             self.DoRemove(None, nodes)
    892         importingStatus = SkillPlanImportingStatus()
    893         sm.GetService('loading').ProgressWnd(importingText, '', 3, 6)
    894         skills = sm.GetService('skills').GetSkills()
    895         for typeID, skillLevel, why in skillList:
    896             blue.pyos.BeNice()
    897             if importingStatus.TooManySkillsAdded() and why == importConst.MANUAL_ADD:
    898                 importingStatus.AddToFailed(typeID, skillLevel, importConst.FAILED_TOO_MANY_SKILLS)
    899                 continue
    900             lowerLevelFailReason = importingStatus.ReasonForFailingForLowerLevel(typeID, skillLevel)
    901             if lowerLevelFailReason is not None and lowerLevelFailReason != importConst.FAILED_SKILL_IN_QUEUE:
    902                 importingStatus.AddToFailed(typeID, skillLevel, lowerLevelFailReason)
    903                 continue
    904             skill = skills.get(typeID, None)
    905             if skill is None or skill.trainedSkillLevel is None:
    906                 importingStatus.AddToFailed(typeID, skillLevel, importConst.FAILED_SKILL_NOT_INJECTED)
    907                 continue
    908             if skill.trainedSkillLevel >= skillLevel:
    909                 importingStatus.AddToFailed(typeID, skillLevel, importConst.FAILED_PREVIOUSLY_TRAINED)
    910                 continue
    911             nextLevel = self.skillQueueSvc.FindNextLevel(typeID, skill.trainedSkillLevel, self.skillQueueSvc.skillQueue)
    912             if get_clone_grade_svc().is_skill_level_restricted(typeID, skillLevel):
    913                 importingStatus.AddToFailed(typeID, skillLevel, importConst.FAILED_OMEGA_RESTRICTION)
    914                 continue
    915             if nextLevel < skillLevel:
    916                 importingStatus.AddToFailed(typeID, skillLevel, importConst.FAILED_INCORRECT_ORDER)
    917                 continue
    918             try:
    919                 self.DoAddSkill(typeID, skillLevel)
    920             except UserError as userError:
    921                 importingStatus.AddToFailed(typeID, skillLevel, userError.msg)
    922             except Exception as e:
    923                 importingStatus.AddToFailed(typeID, skillLevel, 'unknown')
    924             else:
    925                 importingStatus.IncreaseAddedCount()
    926 
    927         sm.GetService('loading').ProgressWnd(importingText, '', 5, 6)
    928         self.UpdateTime()
    929         self.ReloadEntriesIfNeeded()
    930         sm.GetService('loading').ProgressWnd(importingText, '', 6, 6)
    931         wnd = SkillImportStatusWindow.Open()
    932         wnd.LoadInitialImportingStatus(importingStatus, failedLines)
    933 
    934     def ApplySkillPoints(self, *args):
    935         if self.isApplying:
    936             return
    937         self.isApplying = True
    938         try:
    939             isTraining = self.IsTraining()
    940             self.SaveChanges(activate=isTraining)
    941             self.skillQueueSvc.ApplyFreeSkillPointsToQueue()
    942             self.skillQueueSvc.BeginTransaction()
    943             PlaySound('st_allocate_skillpoints_play')
    944         finally:
    945             self.isApplying = False
    946 
    947     def IsTraining(self):
    948         return self.skillQueueSvc.SkillInTraining() is not None