diff --git a/gui/characterSelection.py b/gui/characterSelection.py index d522d4c25..3d20d5589 100644 --- a/gui/characterSelection.py +++ b/gui/characterSelection.py @@ -109,7 +109,6 @@ class CharacterSelection(wx.Panel): def fitChanged(self, event): self.charChoice.Enable(event.fitID != None) - choice = self.charChoice cFit = service.Fit.getInstance() currCharID = choice.GetClientData(choice.GetCurrentSelection()) @@ -117,15 +116,17 @@ class CharacterSelection(wx.Panel): newCharID = fit.character.ID if fit is not None else None if event.fitID is None: self.skillReqsStaticBitmap.SetBitmap(self.cleanSkills) - self.skillReqsStaticBitmap.SetToolTipString("No active fit.") + self.skillReqsStaticBitmap.SetToolTipString("No active fit") else: sCharacter = service.Character.getInstance() reqs = sCharacter.checkRequirements(fit) + sCharacter.skillReqsDict = {'charname':fit.character.name, 'skills':[]} if len(reqs) == 0: - tip = "All prerequisites have been met" + tip = "All skill prerequisites have been met" self.skillReqsStaticBitmap.SetBitmap(self.greenSkills) else: - tip = self._buildSkillsTooltip(reqs) + tip = "Skills required:\n" + tip += self._buildSkillsTooltip(reqs) self.skillReqsStaticBitmap.SetBitmap(self.redSkills) self.skillReqsStaticBitmap.SetToolTipString(tip.strip()) @@ -137,16 +138,24 @@ class CharacterSelection(wx.Panel): event.Skip() - def _buildSkillsTooltip(self, reqs, tabulationLevel = 0): + def _buildSkillsTooltip(self, reqs, currItem = "", tabulationLevel = 0): tip = "" + sCharacter = service.Character.getInstance() if tabulationLevel == 0: for item, subReqs in reqs.iteritems(): - tip += "%s:\n" % item.name - tip += self._buildSkillsTooltip(subReqs, 1) + tip += " %s:\n" % item.name + tip += self._buildSkillsTooltip(subReqs, item.name, 1) else: for name, info in reqs.iteritems(): - level, more = info - tip += "%s%s: %d\n" % (" " * tabulationLevel, name, level) - tip += self._buildSkillsTooltip(more, tabulationLevel + 1) + level, ID, more = info + sCharacter.skillReqsDict['skills'].append({ + 'item' : currItem, + 'skillID' : ID, + 'skill' : name, + 'level' : level, + 'indent' : tabulationLevel + }) + tip += " %s%s: %d\n" % (" " * tabulationLevel, name, level) + tip += self._buildSkillsTooltip(more, currItem, tabulationLevel + 1) return tip diff --git a/gui/mainFrame.py b/gui/mainFrame.py index 880d5a589..bdd29fe2e 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -263,7 +263,9 @@ class MainFrame(wx.Frame): dlg=wx.FileDialog( self, "Open One Or More Fitting Files", - wildcard = "EFT text fitting files (*.cfg)|*.cfg|EvE XML fitting files (*.xml)|*.xml|All Files (*)|*", + wildcard = "EFT text fitting files (*.cfg)|*.cfg|" \ + "EvE XML fitting files (*.xml)|*.xml|" \ + "All Files (*)|*", style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST | wx.FD_MULTIPLE) if (dlg.ShowModal() == wx.ID_OK): self.waitDialog = animUtils.WaitDialog(self, title = "Importing") @@ -335,6 +337,8 @@ class MainFrame(wx.Frame): self.Bind(wx.EVT_MENU, self.importFromClipboard, id=wx.ID_PASTE) # Backup fits self.Bind(wx.EVT_MENU, self.backupToXml, id=menuBar.backupFitsId) + # Export skills needed + self.Bind(wx.EVT_MENU, self.exportSkillsNeeded, id=menuBar.exportSkillsNeededId) # Preference dialog self.Bind(wx.EVT_MENU, self.showPreferenceDialog, id = menuBar.preferencesId) @@ -475,6 +479,31 @@ class MainFrame(wx.Frame): saveDialog.Destroy() + def exportSkillsNeeded(self, event): + sCharacter = service.Character.getInstance() + saveDialog = wx.FileDialog( + self, + "Export Skills Needed As...", + wildcard = "EVEMon skills training file (*.emp)|*.emp|" \ + "EVEMon skills training XML file (*.xml)|*.xml|" \ + "Text skills training file (*.txt)|*.txt", + style = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) + if (saveDialog.ShowModal() == wx.ID_OK): + saveFmtInt = saveDialog.GetFilterIndex() + saveFmt = "" + if saveFmtInt == 0: # Per ordering of wildcards above + saveFmt = "emp" + elif saveFmtInt == 1: + saveFmt = "xml" + else: + saveFmt = "txt" + filePath = saveDialog.GetPath() + self.waitDialog = animUtils.WaitDialog(self) + sCharacter.backupSkills(filePath, saveFmt, self.getActiveFit(), self.closeWaitDialog) + self.waitDialog.ShowModal() + + saveDialog.Destroy() + def closeWaitDialog(self): self.waitDialog.Destroy() diff --git a/gui/mainMenuBar.py b/gui/mainMenuBar.py index 6404242e3..327bc0e60 100644 --- a/gui/mainMenuBar.py +++ b/gui/mainMenuBar.py @@ -30,6 +30,7 @@ class MainMenuBar(wx.MenuBar): self.damagePatternEditorId = wx.NewId() self.graphFrameId = wx.NewId() self.backupFitsId = wx.NewId() + self.exportSkillsNeededId = wx.NewId() self.preferencesId = wx.NewId() self.mainFrame = gui.mainFrame.MainFrame.getInstance() @@ -47,6 +48,7 @@ class MainMenuBar(wx.MenuBar): fileMenu.Append(self.backupFitsId, "&Backup fits", "Backup all fittings to a XML file") fileMenu.Append(wx.ID_OPEN, "&Import\tCTRL+O", "Import a fit into pyfa.") fileMenu.Append(wx.ID_SAVEAS, "&Export\tCTRL+S", "Export the fit to another format.") + fileMenu.Append(self.exportSkillsNeededId, "Export &Skills Needed", "Export skills needed for this fitting") fileMenu.AppendSeparator() fileMenu.Append(wx.ID_EXIT) @@ -105,5 +107,6 @@ class MainMenuBar(wx.MenuBar): enable = event.fitID is not None self.Enable(wx.ID_SAVEAS, enable) self.Enable(wx.ID_COPY, enable) + self.Enable(self.exportSkillsNeededId, enable) event.Skip() diff --git a/service/character.py b/service/character.py index f97a1daa7..97c55ec0a 100644 --- a/service/character.py +++ b/service/character.py @@ -23,8 +23,51 @@ import copy import service import itertools +import os.path +import locale +import threading +import wx +from codecs import open + +from xml.etree import ElementTree +from xml.dom import minidom + +import gzip + +EVEMON_COMPATIBLE_VERSION = "4081" + +class SkillBackupThread(threading.Thread): + def __init__(self, path, saveFmt, activeFit, callback): + threading.Thread.__init__(self) + self.path = path + self.saveFmt = saveFmt + self.activeFit = activeFit + self.callback = callback + + def run(self): + path = self.path + sCharacter = Character.getInstance() + sFit = service.Fit.getInstance() + fit = sFit.getFit(self.activeFit) + backupData = ""; + if self.saveFmt == "xml" or self.saveFmt == "emp": + backupData = sCharacter.exportXml() + else: + backupData = sCharacter.exportText() + + if self.saveFmt == "emp": + with gzip.open(path, "wb") as backupFile: + backupFile.write(backupData) + else: + with open(path, "w", encoding="utf-8") as backupFile: + backupFile.write(backupData) + + wx.CallAfter(self.callback) + class Character(): instance = None + skillReqsDict = {} + @classmethod def getInstance(cls): if cls.instance is None: @@ -32,6 +75,58 @@ class Character(): return cls.instance + def exportText(self): + data = "Pyfa exported plan for \""+self.skillReqsDict['charname']+"\"\n" + data += "=" * 79 + "\n" + data += "\n" + item = "" + for s in self.skillReqsDict['skills']: + if item == "" or not item == s["item"]: + item = s["item"] + data += "-" * 79 + "\n" + data += "Skills required for {}:\n".format(item) + data += "{}{}: {}\n".format(" " * s["indent"], s["skill"], int(s["level"])) + data += "-" * 79 + "\n" + + return data + + def exportXml(self): + root = ElementTree.Element("plan") + root.attrib["name"] = "Pyfa exported plan for "+self.skillReqsDict['charname'] + root.attrib["revision"] = EVEMON_COMPATIBLE_VERSION + + sorts = ElementTree.SubElement(root, "sorting") + sorts.attrib["criteria"] = "None" + sorts.attrib["order"] = "None" + sorts.attrib["groupByPriority"] = "false" + + skillsSeen = set() + + for s in self.skillReqsDict['skills']: + skillKey = str(s["skillID"])+"::"+s["skill"]+"::"+str(int(s["level"])) + if skillKey in skillsSeen: + pass # Duplicate skills confuse EVEMon + else: + skillsSeen.add(skillKey) + entry = ElementTree.SubElement(root, "entry") + entry.attrib["skillID"] = str(s["skillID"]) + entry.attrib["skill"] = s["skill"] + entry.attrib["level"] = str(int(s["level"])) + entry.attrib["priority"] = "3" + entry.attrib["type"] = "Prerequisite" + notes = ElementTree.SubElement(entry, "notes") + notes.text = entry.attrib["skill"] + + tree = ElementTree.ElementTree(root) + data = ElementTree.tostring(root, 'utf-8') + prettydata = minidom.parseString(data).toprettyxml(indent=" ") + + return prettydata + + def backupSkills(self, path, saveFmt, activeFit, callback): + thread = SkillBackupThread(path, saveFmt, activeFit, callback) + thread.start() + def all0(self): all0 = eos.types.Character.getAll0() eos.db.commit() @@ -169,10 +264,11 @@ class Character(): def _checkRequirements(self, fit, char, subThing, reqs): for req, level in subThing.requiredSkills.iteritems(): name = req.name + ID = req.ID info = reqs.get(name) currLevel, subs = info if info is not None else 0, {} if level > currLevel and (char is None or char.getSkill(req).level < level): - reqs[name] = (level, subs) + reqs[name] = (level, ID, subs) self._checkRequirements(fit, char, req, subs) return reqs