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)