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

skillPlanEditorContents.py (19754B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\skillPlan\skillPlanEditor\skillPlanEditorContents.py
      2 import blue
      3 import evetypes
      4 import itertoolsext
      5 import uthread2
      6 from carbon.client.script.environment.AudioUtil import PlaySound
      7 from carbonui import uiconst, fontconst
      8 from carbonui.control.dragdrop import dragdata, dragDropUtil
      9 from carbonui.control.scrollContainer import ScrollContainer
     10 from carbonui.primitives.container import Container
     11 from carbonui.uianimations import animations
     12 from carbonui.uicore import uicore
     13 from eve.client.script.ui import eveColor
     14 from eve.client.script.ui.control.eveLabel import EveCaptionLarge
     15 from eve.client.script.ui.control.eveLoadingWheel import LoadingWheel
     16 from eve.client.script.ui.control.utilMenu import UtilMenu
     17 from eve.client.script.ui.quickFilter import QuickFilterEdit
     18 from eve.client.script.ui.skillPlan import skillPlanUISignals, skillPlanUtil
     19 from eve.client.script.ui.skillPlan.controls.skillPlanLine import SkillPlanLine
     20 from eve.client.script.ui.skillPlan.skillPlanConst import NO_REQ
     21 from eve.client.script.ui.skillPlan.skillPlanEditor.dragIndicatorLine import DragIndicatorLine
     22 from eve.client.script.ui.skillPlan.skillPlanEditor.skillPlanDropIndicatorCont import SkillPlanDropIndicatorCont
     23 from eve.client.script.ui.skillPlan.skillPlanEditor.skillPlanEditorSkillEntry import INVALID_LINE_COLOR, SkillPlanEditorSkillEntry, VALID_LINE_COLOR
     24 from eve.client.script.ui.skillPlan.skillPlanUtil import GetPreReqsAndRequiredForSkillLevel, GetRequirementStateForEntry, IsTypeValidMilestone
     25 from eve.common.lib import appConst
     26 from eve.common.lib import appConst as const
     27 from eveui import clipboard
     28 from localization import GetByLabel
     29 from shipfitting import INVALID_FITTING_ID
     30 from shipfitting.client.link import parse_fitting_url
     31 from skills.skillConst import skill_max_level
     32 SCROLL_LOADING_OPACITY = 0.5
     33 ASYNC_LOAD_THRESHOLD = 30
     34 
     35 class SkillPlanEditorContents(Container):
     36 
     37     def ApplyAttributes(self, attributes):
     38         super(SkillPlanEditorContents, self).ApplyAttributes(attributes)
     39         self.skillPlan = None
     40         self.entries = set()
     41         skillPlanUISignals.on_skill_requirements_changed.connect(self.OnSkillRequirementsChanged)
     42         skillPlanUISignals.on_skill_plan_cleared.connect(self.OnSkillPlanCleared)
     43         self.topCont = Container(name='topCont', parent=self, align=uiconst.TOTOP, height=70)
     44         self._ConstructTopCont()
     45         SkillPlanLine(parent=self, align=uiconst.TOTOP, state=uiconst.UI_DISABLED, padTop=6)
     46         self.dropIndicatorCont = SkillPlanDropIndicatorCont(parent=self, align=uiconst.TOBOTTOM)
     47         self.dropIndicatorCont.OnDragEnter = self.OnDropContDragEnter
     48         self.dropIndicatorCont.OnDropData = self.OnDropContDropData
     49         self.scroll = ScrollContainer(parent=self, showUnderlay=False, align=uiconst.TOALL, state=uiconst.UI_DISABLED, opacity=SCROLL_LOADING_OPACITY, padTop=20)
     50         self.loadingWheel = LoadingWheel(parent=self, align=uiconst.CENTER)
     51         self.scroll.onDropDataSignal.connect(self.OnDropData)
     52         self.scroll.onDragEnterSignal.connect(self.OnScrollDragEnter)
     53         self.scroll.onDragExitSignal.connect(self.OnScrollDragExit)
     54         self.dragIndicator = None
     55         SkillPlanLine(parent=self, align=uiconst.TOBOTTOM, idx=0, state=uiconst.UI_DISABLED)
     56         self.CheckShowNoContentHint()
     57 
     58     def Close(self):
     59         super(SkillPlanEditorContents, self).Close()
     60         skillPlanUISignals.on_skill_requirements_changed.disconnect(self.OnSkillRequirementsChanged)
     61         skillPlanUISignals.on_skill_plan_cleared.disconnect(self.OnSkillPlanCleared)
     62 
     63     def _ConstructTopCont(self):
     64         self.caption = EveCaptionLarge(parent=self.topCont, name='skillPlanOverviewLabel', align=uiconst.TOPLEFT, text=GetByLabel('UI/SkillPlan/SkillPlan'))
     65         UtilMenu(parent=self.topCont, align=uiconst.TOPRIGHT, texturePath='res:/UI/Texture/Icons/77_32_49.png', pos=(-4, 4, 24, 24), GetUtilMenu=self.GetUtilMenu, iconSize=24)
     66         self.quickFilterEdit = QuickFilterEdit(parent=self.topCont, align=uiconst.BOTTOMRIGHT, width=120, height=30, top=4, callback=self.OnQuickFilterEdit)
     67 
     68     def OnQuickFilterEdit(self):
     69         self.ReconstructScrollEntries()
     70 
     71     def GetUtilMenu(self, menuParent):
     72         menuParent.AddIconEntry(icon='res:/UI/Texture/classes/UtilMenu/BulletIcon.png', text=GetByLabel('UI/SkillPlan/CopySkillsToClipboard'), callback=self.CopySkillsToClipboard)
     73         hint = GetByLabel('UI/SkillQueue/ImportSkillplanFormatHint', typeID=appConst.typeSpaceshipCommand)
     74         menuParent.AddIconEntry(icon='res:/UI/Texture/classes/UtilMenu/BulletIcon.png', text=GetByLabel('UI/SkillPlan/ImportSkillsFromClipboard'), hint=hint, callback=self.ImportSkillsFromClipboard)
     75         menuParent.AddIconEntry(icon='res:/UI/Texture/classes/UtilMenu/BulletIcon.png', text=GetByLabel('UI/SkillPlan/ClearSkills'), callback=self.ClearSkills)
     76 
     77     def ClearSkills(self):
     78         self.skillPlan.ClearSkillRequirements()
     79         self.skillPlan.ClearMilestones()
     80         skillPlanUISignals.on_skill_plan_cleared(self.skillPlan.GetID())
     81 
     82     def ImportSkillsFromClipboard(self):
     83         skillplanImporter = sm.GetService('skillqueue').GetSkillPlanImporter()
     84         skillRequirements, failedLines = skillplanImporter.GetSkillsAndLevelsFromText(clipboard.get())
     85         if skillRequirements:
     86             self.skillPlan.AppendSkillRequirements(skillRequirements)
     87 
     88     def CopySkillsToClipboard(self):
     89         clipboard.set(self.skillPlan.GetRequirementsClipboardText())
     90 
     91     def OnSkillRequirementsChanged(self, skillPlanID):
     92         if skillPlanID == self.skillPlan.GetID():
     93             self.ReconstructScrollEntries()
     94             self.UpdateSkillCountTotal()
     95 
     96     def OnSkillPlanCleared(self, skillPlanID):
     97         if self.skillPlan and self.skillPlan.GetID() == skillPlanID:
     98             self.entries.clear()
     99             self.scroll.Flush()
    100             self.CheckShowNoContentHint()
    101 
    102     def CheckShowNoContentHint(self):
    103         numShown = len([ entry for entry in self.entries if entry.display ])
    104         if not self.skillPlan:
    105             return
    106         if not self.skillPlan.GetSkillRequirements():
    107             self.scroll.ShowNoContentHint(GetByLabel('UI/SkillPlan/NoSkillsInPlanHint'))
    108         elif not numShown:
    109             self.scroll.ShowNoContentHint(GetByLabel('UI/CharacterSheet/CharacterSheetWindow/SkillTabs/NoSkillsMatchingFilters'))
    110         else:
    111             self.scroll.HideNoContentHint()
    112 
    113     def OnDropData(self, dropObj, entries):
    114         dragData = entries[0]
    115         guid = getattr(dragData, '__guid__', None)
    116         if isinstance(dragData, dragdata.SkillLevelDragData):
    117             if uicore.uilib.Key(uiconst.VK_SHIFT):
    118                 if dragData.level < skill_max_level:
    119                     PlaySound(uiconst.SOUND_ADD_OR_USE)
    120                     self.skillPlan.AppendSkillRequirement(dragData.typeID)
    121             else:
    122                 self._MoveEntryToBottom(dragData.typeID, dragData.level)
    123         elif isinstance(dragData, dragdata.TypeDragData) and evetypes.IsSkill(dragData.typeID):
    124             PlaySound(uiconst.SOUND_ADD_OR_USE)
    125             self.skillPlan.AppendSkillRequirement(dragData.typeID)
    126         elif guid is not None and guid == 'listentry.FittingEntry':
    127             self._OnDropFitting(dragData.fitting)
    128         elif guid is not None and guid == 'TextLink':
    129             fittingString, _ = parse_fitting_url(dragData.url)
    130             fitting, _ = sm.GetService('fittingSvc').GetFittingFromString(fittingString)
    131             if fitting != INVALID_FITTING_ID:
    132                 self._OnDropFitting(fitting)
    133         else:
    134             self._OnTypeDropData(dragData)
    135         self.HideDragIndicator()
    136 
    137     def _OnDropFitting(self, fitting):
    138         typeIDs = [fitting.shipTypeID]
    139         rackTypes = sm.GetService('fittingSvc').GetTypesByRack(fitting)
    140 
    141         def _ExtractTypeIDsFromCategory(categoryName):
    142             if categoryName in rackTypes:
    143                 for typeID, _ in rackTypes[categoryName].iteritems():
    144                     if typeID not in typeIDs:
    145                         typeIDs.append(typeID)
    146 
    147                 rackTypes.pop(categoryName)
    148 
    149         _ExtractTypeIDsFromCategory('subSystems')
    150         _ExtractTypeIDsFromCategory('hiSlots')
    151         _ExtractTypeIDsFromCategory('medSlots')
    152         _ExtractTypeIDsFromCategory('lowSlots')
    153         _ExtractTypeIDsFromCategory('rigSlots')
    154         _ExtractTypeIDsFromCategory('charges')
    155         _ExtractTypeIDsFromCategory('fighters')
    156         _ExtractTypeIDsFromCategory('drones')
    157         _ExtractTypeIDsFromCategory('implants')
    158         _ExtractTypeIDsFromCategory('modules')
    159         for rack, contents in rackTypes.iteritems():
    160             if len(contents) > 0:
    161                 for typeID, _ in contents.iteritems():
    162                     if typeID not in typeIDs:
    163                         typeIDs.append(typeID)
    164 
    165         self.BulkAddSkillRequirements(typeIDs)
    166 
    167     def _OnTypeDropData(self, dragData):
    168         typeID = dragDropUtil.GetTypeID(dragData)
    169         if typeID:
    170             self._AddRequirementsForType(typeID)
    171 
    172     def _AddRequirementsForType(self, typeID):
    173         if not IsTypeValidMilestone(typeID):
    174             return
    175         PlaySound(uiconst.SOUND_ADD_OR_USE)
    176         if self.skillPlan.HasEmptyMilestoneSlots():
    177             if evetypes.GetCategoryID(typeID) == const.categorySkill:
    178                 self.skillPlan.AddAllSkillsRequiredFor(typeID, 1)
    179                 self.skillPlan.AddSkillRequirementMilestone(typeID, 1)
    180             else:
    181                 self.skillPlan.AddTypeIDMilestone(typeID)
    182         else:
    183             self.skillPlan.AddAllSkillsRequiredFor(typeID)
    184 
    185     def BulkAddSkillRequirements(self, typeIDs):
    186         if not self.skillPlan:
    187             return
    188         PlaySound(uiconst.SOUND_ADD_OR_USE)
    189         uthread2.start_tasklet(self._BulkAddSkillRequirements, typeIDs)
    190 
    191     def _BulkAddSkillRequirements(self, typeIDs):
    192         if not self.skillPlan:
    193             return
    194         self._ShowLoadingWheel()
    195         self.skillPlan.BulkAddAllSkillsRequiredFor(typeIDs)
    196         self._HideLoadingWheel()
    197 
    198     def SetSkillPlan(self, skillPlan):
    199         self.skillPlan = skillPlan
    200         uthread2.start_tasklet(self.ConstructScrollEntries)
    201         self.UpdateSkillCountTotal()
    202 
    203     def ConstructScrollEntries(self):
    204         requiresAsyncLoading = len(self.skillPlan.GetSkillRequirements()) > ASYNC_LOAD_THRESHOLD
    205         if requiresAsyncLoading:
    206             skillPlanUISignals.on_contents_list_started_reconstructing()
    207         for typeID, level in self.skillPlan.GetSkillRequirements():
    208             entry = self.ConstructScrollEntry(typeID, level)
    209             self.entries.add(entry)
    210             if requiresAsyncLoading:
    211                 blue.pyos.BeNice()
    212 
    213         self.CheckShowNoContentHint()
    214         self.scroll.state = uiconst.UI_NORMAL
    215         self._HideLoadingWheel()
    216         if requiresAsyncLoading:
    217             skillPlanUISignals.on_contents_list_finished_reconstructing()
    218 
    219     def ReconstructScrollEntries(self):
    220         currentLen = len(self.entries)
    221         newLen = len(self.skillPlan.GetSkillRequirements())
    222         requiresAsyncLoading = newLen - currentLen > ASYNC_LOAD_THRESHOLD
    223         if requiresAsyncLoading:
    224             skillPlanUISignals.on_contents_list_started_reconstructing()
    225         selectedEntry = itertoolsext.first_or_default(self.entries, lambda x: x.isSelected)
    226         if selectedEntry and (selectedEntry.typeID, selectedEntry.level) in self.skillPlan.GetSkillRequirements():
    227             selectedTypeID, selectedLevel = selectedEntry.typeID, selectedEntry.level
    228             prereqs, reqForTypeIDs = GetPreReqsAndRequiredForSkillLevel(selectedEntry.typeID, selectedEntry.level)
    229         else:
    230             selectedTypeID = selectedLevel = None
    231             prereqs, reqForTypeIDs = {}, set()
    232         entriesToReUse = {(entry.typeID, entry.level):entry for entry in self.entries}
    233         for entry in self.entries:
    234             if entry.GetParent() == self.scroll:
    235                 entry.SetParent(None)
    236 
    237         self.entries.clear()
    238         for typeID, level in self.skillPlan.GetSkillRequirements():
    239             entry = entriesToReUse.pop((typeID, level), None)
    240             if entry:
    241                 if entry.GetParent() != self.scroll:
    242                     entry.SetParent(self.scroll)
    243                 entry.UpdatePrimaryButton()
    244             else:
    245                 entry = self.ConstructScrollEntry(typeID, level)
    246             if selectedEntry:
    247                 reqState = GetRequirementStateForEntry(selectedTypeID, selectedLevel, entry, prereqs, reqForTypeIDs)
    248             else:
    249                 reqState = NO_REQ
    250             entry.UpdateReqState(reqState)
    251             self.entries.add(entry)
    252             entry.display = self.IsEntryShown(entry)
    253             if requiresAsyncLoading:
    254                 self._UpdateSkillCountPartial()
    255                 blue.pyos.BeNice()
    256 
    257         for entry in entriesToReUse.itervalues():
    258             entry.Close()
    259 
    260         self.CheckShowNoContentHint()
    261         if requiresAsyncLoading:
    262             skillPlanUISignals.on_contents_list_finished_reconstructing()
    263 
    264     def IsEntryShown(self, entry):
    265         filterTxt = self.quickFilterEdit.GetValue()
    266         if not filterTxt:
    267             return True
    268         return filterTxt.lower() in evetypes.GetName(entry.typeID).lower()
    269 
    270     def ConstructScrollEntry(self, typeID, level, index = -1):
    271         entry = SkillPlanEditorSkillEntry(parent=self.scroll, typeID=typeID, level=level, idx=index, skillPlan=self.skillPlan, padBottom=8)
    272         entry.onClickSignal.connect(self.OnEntryClicked)
    273         entry.onDblClickSignal.connect(self.OnEntryDblClicked)
    274         entry.onDropDataSignal.connect(self.OnEntryDropData)
    275         entry.onDragEnterSignal.connect(self.OnEntryDragEnter)
    276         return entry
    277 
    278     def OnEntryDblClicked(self, entry):
    279         return self.skillPlan.AppendSkillRequirement(entry.typeID)
    280 
    281     def GetEntry(self, typeID, level):
    282         for entry in self.entries:
    283             if entry.typeID == typeID and entry.level == level:
    284                 return entry
    285 
    286     def OnEntryClicked(self, clickedEntry):
    287         prereqs, reqForTypeIDs = GetPreReqsAndRequiredForSkillLevel(clickedEntry.typeID, clickedEntry.level)
    288         if uicore.uilib.Key(uiconst.VK_CONTROL):
    289             deselectEntry = itertoolsext.first_or_default(self.entries, lambda x: x.isSelected) == clickedEntry
    290         else:
    291             deselectEntry = False
    292         for entry in self.entries:
    293             if entry == clickedEntry:
    294                 if deselectEntry:
    295                     entry.SetDeselected()
    296                 else:
    297                     entry.SetSelected()
    298                 entry.UpdateReqState(NO_REQ)
    299             else:
    300                 entry.SetDeselected()
    301                 if deselectEntry:
    302                     entry.UpdateReqState(NO_REQ)
    303                 else:
    304                     reqState = GetRequirementStateForEntry(clickedEntry.typeID, clickedEntry.level, entry, prereqs, reqForTypeIDs)
    305                     entry.UpdateReqState(reqState)
    306 
    307     def OnEntryDragEnter(self, dragEntry, dragData):
    308         isValidMove = True
    309         if isinstance(dragData, dragdata.SkillLevelDragData):
    310             potentialDropIndex = self.skillPlan.GetSkillRequirementIndex(dragEntry.typeID, dragEntry.level)
    311             originalIndex = self.skillPlan.GetSkillRequirementIndex(dragData.typeID, dragData.level)
    312             isValidMove = self.skillPlan.IsSkillRequirementMoveValid(originalIndex, potentialDropIndex)
    313         dragEntry.ShowIndicatorLine(isValidMove)
    314 
    315     def OnEntryDropData(self, dropEntry, entries):
    316         dragData = entries[0]
    317         guid = getattr(dragData, '__guid__', None)
    318         if isinstance(dragData, dragdata.SkillLevelDragData):
    319             if uicore.uilib.Key(uiconst.VK_SHIFT):
    320                 if dragData.level < skill_max_level:
    321                     index = self.skillPlan.GetSkillRequirementIndex(dropEntry.typeID, dropEntry.level)
    322                     nextLevel = self.skillPlan.GetNextLevel(dragData.typeID)
    323                     if nextLevel and self.skillPlan.IsSkillInsertionValid(dragData.typeID, nextLevel, index):
    324                         self.skillPlan.InsertSkillRequirement(dragData.typeID, level=nextLevel, aboveTypeID=dropEntry.typeID, aboveLevel=dropEntry.level)
    325             else:
    326                 self._MoveEntryAbove(dragData.typeID, dragData.level, dropEntry.typeID, dropEntry.level)
    327         elif isinstance(dragData, dragdata.TypeDragData) and evetypes.IsSkill(dragData.typeID):
    328             PlaySound(uiconst.SOUND_ADD_OR_USE)
    329             self.skillPlan.InsertSkillRequirement(dragData.typeID, aboveTypeID=dropEntry.typeID, aboveLevel=dropEntry.level)
    330         elif guid is not None and guid == 'listentry.FittingEntry':
    331             self._OnDropFitting(dragData.fitting)
    332         elif guid is not None and guid == 'TextLink':
    333             fittingString, _ = parse_fitting_url(dragData.url)
    334             fitting, _ = sm.GetService('fittingSvc').GetFittingFromString(fittingString)
    335             if fitting != INVALID_FITTING_ID:
    336                 self._OnDropFitting(fitting)
    337         else:
    338             self._OnTypeDropData(dragData)
    339 
    340     def _MoveEntryAbove(self, typeID, level, aboveTypeID = None, aboveLevel = None):
    341         self.skillPlan.MoveSkillRequirement(typeID, level, aboveTypeID, aboveLevel)
    342 
    343     def _MoveEntryToBottom(self, typeID, level):
    344         self._MoveEntryAbove(typeID, level)
    345 
    346     def OnDropContDragEnter(self, dragObject, entries):
    347         uthread2.StartTasklet(self.dropIndicatorCont.OnDragEnterFromParent, entries, self.skillPlan, self.scroll)
    348 
    349     def OnDropContDropData(self, dropObj, entries):
    350         if self.scroll.IsVerticalScrollBarVisible() and self.scroll.GetPositionVertical() >= 1.0:
    351             self.OnDropData(dropObj, entries)
    352         self.dropIndicatorCont.HideIndicator()
    353 
    354     def OnScrollDragEnter(self, dragObject, entries):
    355         dragData = entries[0]
    356         if isinstance(dragData, dragdata.SkillLevelDragData):
    357             originalIndex = self.skillPlan.GetSkillRequirementIndex(dragData.typeID, dragData.level)
    358             dropIndex = len(self.skillPlan.GetSkillRequirements())
    359             isValidMove = self.skillPlan.IsSkillRequirementMoveValid(originalIndex, dropIndex)
    360             self.ShowDragIndicator(isValidMove)
    361         elif skillPlanUtil.IsValidContentsDragData(dragData):
    362             self.ShowDragIndicator()
    363 
    364     def ShowDragIndicator(self, isValid = True):
    365         if not self.dragIndicator:
    366             self.dragIndicator = DragIndicatorLine(parent=self.scroll, align=uiconst.TOTOP)
    367         self.dragIndicator.color = VALID_LINE_COLOR if isValid else INVALID_LINE_COLOR
    368 
    369     def OnScrollDragExit(self, dragObject, entries):
    370         self.HideDragIndicator()
    371 
    372     def HideDragIndicator(self):
    373         if self.dragIndicator:
    374             self.dragIndicator.Close()
    375             self.dragIndicator = None
    376 
    377     def _UpdateSkillCountPartial(self):
    378         numSkills = len(self.entries)
    379         self._SetSkillCount(numSkills)
    380 
    381     def UpdateSkillCountTotal(self):
    382         if self.skillPlan:
    383             numSkills = len(self.skillPlan.GetSkillRequirements())
    384             self._SetSkillCount(numSkills)
    385 
    386     def _SetSkillCount(self, numSkills):
    387         numSkillsTxt = ' <color=%s><fontsize=%s>%s</fontsize></color>' % (eveColor.LED_GREY_HEX, fontconst.EVE_LARGE_FONTSIZE, str(numSkills))
    388         self.caption.SetText(GetByLabel('UI/SkillPlan/SkillPlan') + numSkillsTxt)
    389 
    390     def _HideLoadingWheel(self):
    391         animations.FadeTo(self.scroll, self.scroll.opacity, 1.0, duration=0.3)
    392         self.loadingWheel.Hide()
    393 
    394     def _ShowLoadingWheel(self):
    395         self.loadingWheel.Show()
    396         animations.FadeTo(self.scroll, self.scroll.opacity, SCROLL_LOADING_OPACITY, duration=0.3)