# ============================================================================= # Copyright (C) 2010 Diego Duclos # # This file is part of pyfa. # # pyfa is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # pyfa is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with pyfa. If not, see . # ============================================================================= import datetime import itertools import os.path import threading import time import webbrowser from codecs import open from time import gmtime, strftime # noinspection PyPackageRequirements import wx import wx.adv from logbook import Logger # noinspection PyPackageRequirements from wx.lib.inspection import InspectionTool import config import gui.fitCommands as cmd import gui.globalEvents as GE from eos.config import gamedata_date, gamedata_version from eos.modifiedAttributeDict import ModifiedAttributeDict from graphs import GraphFrame from gui.additionsPane import AdditionsPane from gui.bitmap_loader import BitmapLoader from gui.builtinMarketBrowser.events import ItemSelected from gui.builtinShipBrowser.events import FitSelected, ImportSelected, Stage3Selected # noinspection PyUnresolvedReferences from gui.builtinViews import emptyView, entityEditor, fittingView, implantEditor # noqa: F401 from gui.characterEditor import CharacterEditor from gui.characterSelection import CharacterSelection from gui.chrome_tabs import ChromeNotebook from gui.copySelectDialog import CopySelectDialog from gui.devTools import DevTools from gui.esiFittings import EveFittings, ExportToEve, SsoCharacterMgmt from gui.mainMenuBar import MainMenuBar from gui.marketBrowser import MarketBrowser from gui.multiSwitch import MultiSwitch from gui.patternEditor import DmgPatternEditor from gui.preferenceDialog import PreferenceDialog from gui.setEditor import ImplantSetEditor from gui.shipBrowser import ShipBrowser from gui.statsPane import StatsPane from gui.targetProfileEditor import TargetProfileEditor from gui.updateDialog import UpdateDialog from gui.utils.clipboard import fromClipboard, toClipboard from gui.utils.progressHelper import ProgressHelper from eos.const import FittingSlot as es_Slot from eos.saveddata.character import Skill from eos.saveddata.fighter import Fighter as es_Fighter from eos.saveddata.module import Module as es_Module from service.character import Character from service.esi import Esi from service.fit import Fit from service.port import Port from service.price import Price from service.settings import HTMLExportSettings, SettingsProvider from service.update import Update _t = wx.GetTranslation pyfalog = Logger(__name__) disableOverrideEditor = False try: from gui.propertyEditor import AttributeEditor except ImportError as e: AttributeEditor = None pyfalog.warning("Error loading Attribute Editor: %s.\nAccess to Attribute Editor is disabled." % e.message) disableOverrideEditor = True pyfalog.debug("Done loading mainframe imports") # dummy panel(no paint no erasebk) class PFPanel(wx.Panel): def __init__(self, parent): wx.Panel.__init__(self, parent) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnBkErase) def OnPaint(self, event): event.Skip() def OnBkErase(self, event): pass class OpenFitsThread(threading.Thread): def __init__(self, fits, callback): threading.Thread.__init__(self) self.name = "LoadingOpenFits" self.mainFrame = MainFrame.getInstance() self.callback = callback self.fits = fits self.running = True self.start() def run(self): # `startup` tells FitSpawner that we are loading fits are startup, and # has 3 values: # False = Set as default in FitSpawner itself, never set here # 1 = Create new fit page, but do not calculate page # 2 = Create new page and calculate # We use 1 for all fits except the last one where we use 2 so that we # have correct calculations displayed at startup for fitID in self.fits[:-1]: if self.running: wx.PostEvent(self.mainFrame, FitSelected(fitID=fitID, startup=1)) if self.running: wx.PostEvent(self.mainFrame, FitSelected(fitID=self.fits[-1], startup=2)) wx.CallAfter(self.callback) def stop(self): self.running = False class MainFrame(wx.Frame): __instance = None @classmethod def getInstance(cls): return cls.__instance if cls.__instance is not None else MainFrame() def __init__(self, title="pyfa"): pyfalog.debug("Initialize MainFrame") self.title = title super().__init__(None, wx.ID_ANY, self.title) self.supress_left_up = False MainFrame.__instance = self # Load stored settings (width/height/maximized..) self.LoadMainFrameAttribs() self.disableOverrideEditor = disableOverrideEditor # Fix for msw (have the frame background color match panel color if 'wxMSW' in wx.PlatformInfo: self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)) # Load and set the icon for pyfa main window i = wx.Icon(BitmapLoader.getBitmap("pyfa", "gui")) self.SetIcon(i) # Create the layout and windows mainSizer = wx.BoxSizer(wx.HORIZONTAL) self.browser_fitting_split = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE) self.fitting_additions_split = wx.SplitterWindow(self.browser_fitting_split, style=wx.SP_LIVE_UPDATE) mainSizer.Add(self.browser_fitting_split, 1, wx.EXPAND | wx.LEFT, 2) self.fitMultiSwitch = MultiSwitch(self.fitting_additions_split) self.additionsPane = AdditionsPane(self.fitting_additions_split, self) self.notebookBrowsers = ChromeNotebook(self.browser_fitting_split, False) marketImg = BitmapLoader.getImage("market_small", "gui") shipBrowserImg = BitmapLoader.getImage("ship_small", "gui") self.marketBrowser = MarketBrowser(self.notebookBrowsers) self.notebookBrowsers.AddPage(self.marketBrowser, _t("Market"), image=marketImg, closeable=False) self.marketBrowser.splitter.SetSashPosition(self.marketHeight) self.shipBrowser = ShipBrowser(self.notebookBrowsers) self.notebookBrowsers.AddPage(self.shipBrowser, _t("Fittings"), image=shipBrowserImg, closeable=False) self.notebookBrowsers.SetSelection(1) self.browser_fitting_split.SplitVertically(self.notebookBrowsers, self.fitting_additions_split) self.browser_fitting_split.SetMinimumPaneSize(204) self.browser_fitting_split.SetSashPosition(self.browserWidth) self.fitting_additions_split.SplitHorizontally(self.fitMultiSwitch, self.additionsPane, -200) self.fitting_additions_split.SetMinimumPaneSize(200) self.fitting_additions_split.SetSashPosition(self.fittingHeight) self.fitting_additions_split.SetSashGravity(1.0) cstatsSizer = wx.BoxSizer(wx.VERTICAL) self.charSelection = CharacterSelection(self) cstatsSizer.Add(self.charSelection, 0, wx.EXPAND) # @todo pheonix: fix all stats stuff self.statsPane = StatsPane(self) cstatsSizer.Add(self.statsPane, 0, wx.EXPAND) mainSizer.Add(cstatsSizer, 0, wx.EXPAND) self.SetSizer(mainSizer) # Add menu self.addPageId = wx.NewId() self.closePageId = wx.NewId() self.closeAllPagesId = wx.NewId() self.hiddenGraphsId = wx.NewId() self.widgetInspectMenuID = wx.NewId() self.SetMenuBar(MainMenuBar(self)) self.registerMenu() # Internal vars to keep track of other windows self.statsWnds = [] self.activeStatsWnd = None self.Bind(wx.EVT_CLOSE, self.OnClose) # Show ourselves self.Show() self.LoadPreviousOpenFits() # Check for updates self.sUpdate = Update.getInstance() self.sUpdate.CheckUpdate(self.ShowUpdateBox) self.Bind(GE.EVT_SSO_LOGIN, self.onSSOLogin) @property def command(self) -> wx.CommandProcessor: return Fit.getCommandProcessor(self.getActiveFit()) def getCommandForFit(self, fitID) -> wx.CommandProcessor: return Fit.getCommandProcessor(fitID) def ShowUpdateBox(self, release, version): with UpdateDialog(self, release, version) as dlg: dlg.ShowModal() def LoadPreviousOpenFits(self): sFit = Fit.getInstance() self.prevOpenFits = SettingsProvider.getInstance().getSettings("pyfaPrevOpenFits", {"enabled": False, "pyfaOpenFits": []}) fits = self.prevOpenFits['pyfaOpenFits'] # Remove any fits that cause exception when fetching (non-existent fits) for id in fits[:]: try: fit = sFit.getFit(id, basic=True) if fit is None: fits.remove(id) except (KeyboardInterrupt, SystemExit): raise except: fits.remove(id) if not self.prevOpenFits['enabled'] or len(fits) == 0: # add blank page if there are no fits to be loaded self.fitMultiSwitch.AddPage() return self.waitDialog = wx.BusyInfo(_t("Loading previous fits..."), parent=self) OpenFitsThread(fits, self.closeWaitDialog) def _getDisplayData(self): displayData = [] for i in range(wx.Display.GetCount()): display = wx.Display(i) displayData.append(display.GetClientArea()) return displayData def LoadMainFrameAttribs(self): mainFrameDefaultAttribs = { "wnd_display": 0, "wnd_x": 0, "wnd_y": 0, "wnd_width": 1000, "wnd_height": 700, "wnd_maximized": False, "browser_width": 300, "market_height": 0, "fitting_height": -200 } self.mainFrameAttribs = SettingsProvider.getInstance().getSettings( "pyfaMainWindowAttribs", mainFrameDefaultAttribs) wndDisplay = self.mainFrameAttribs["wnd_display"] displayData = self._getDisplayData() try: selectedDisplayData = displayData[wndDisplay] except IndexError: selectedDisplayData = displayData[0] dspX, dspY, dspW, dspH = selectedDisplayData if self.mainFrameAttribs["wnd_maximized"]: wndW = mainFrameDefaultAttribs["wnd_width"] wndH = mainFrameDefaultAttribs["wnd_height"] wndX = min(mainFrameDefaultAttribs["wnd_x"], dspW * 0.75) wndY = min(mainFrameDefaultAttribs["wnd_y"], dspH * 0.75) self.Maximize() else: wndW = self.mainFrameAttribs["wnd_width"] wndH = self.mainFrameAttribs["wnd_height"] wndX = min(self.mainFrameAttribs["wnd_x"], dspW * 0.75) wndY = min(self.mainFrameAttribs["wnd_y"], dspH * 0.75) self.SetPosition((dspX + wndX, dspY + wndY)) self.SetSize((wndW, wndH)) self.SetMinSize((mainFrameDefaultAttribs["wnd_width"], mainFrameDefaultAttribs["wnd_height"])) self.browserWidth = self.mainFrameAttribs["browser_width"] self.marketHeight = self.mainFrameAttribs["market_height"] self.fittingHeight = self.mainFrameAttribs["fitting_height"] def UpdateMainFrameAttribs(self): if self.IsIconized(): return wndGlobalX, wndGlobalY = self.GetPosition() displayData = self._getDisplayData() wndDisplay = 0 wndX = 0 wndY = 0 for i, (sdX, sdY, sdW, sdH) in enumerate(displayData): wndRelX = wndGlobalX - sdX wndRelY = wndGlobalY - sdY if 0 <= wndRelX < sdW and 0 <= wndRelY < sdH: wndDisplay = i wndX = wndRelX wndY = wndRelY break self.mainFrameAttribs["wnd_display"] = wndDisplay self.mainFrameAttribs["wnd_x"] = wndX self.mainFrameAttribs["wnd_y"] = wndY wndW, wndH = self.GetSize() self.mainFrameAttribs["wnd_width"] = wndW self.mainFrameAttribs["wnd_height"] = wndH self.mainFrameAttribs["wnd_maximized"] = self.IsMaximized() self.mainFrameAttribs["browser_width"] = self.notebookBrowsers.GetSize()[0] self.mainFrameAttribs["market_height"] = self.marketBrowser.marketView.GetSize()[1] self.mainFrameAttribs["fitting_height"] = self.fitting_additions_split.GetSashPosition() def SetActiveStatsWindow(self, wnd): self.activeStatsWnd = wnd def GetActiveStatsWindow(self): if self.activeStatsWnd in self.statsWnds: return self.activeStatsWnd if len(self.statsWnds) > 0: return self.statsWnds[len(self.statsWnds) - 1] else: return None def RegisterStatsWindow(self, wnd): self.statsWnds.append(wnd) def UnregisterStatsWindow(self, wnd): self.statsWnds.remove(wnd) def getActiveFit(self): p = self.fitMultiSwitch.GetSelectedPage() m = getattr(p, "getActiveFit", None) return m() if m is not None else None def getActiveView(self): self.fitMultiSwitch.GetSelectedPage() def CloseCurrentPage(self, evt): ms = self.fitMultiSwitch page = ms.GetSelection() if page is not None: ms.DeletePage(page) def CloseAllPages(self, evt): ms = self.fitMultiSwitch for _ in range(ms.GetPageCount()): ms.DeletePage(0) def OnClose(self, event): self.UpdateMainFrameAttribs() # save open fits self.prevOpenFits['pyfaOpenFits'] = [] # clear old list for page in self.fitMultiSwitch._pages: m = getattr(page, "getActiveFit", None) if m is not None: self.prevOpenFits['pyfaOpenFits'].append(m()) # save all teh settingz SettingsProvider.getInstance().saveAll() event.Skip() def ExitApp(self, event): self.Close() event.Skip() def ShowAboutBox(self, evt): info = wx.adv.AboutDialogInfo() info.Name = "pyfa" time = datetime.datetime.fromtimestamp(int(gamedata_date)).strftime('%Y-%m-%d %H:%M:%S') info.Version = config.getVersion() + '\nEVE Data Version: {} ({})'.format(gamedata_version, time) # gui.aboutData.versionString # # try: # import matplotlib # matplotlib_version = matplotlib.__version__ # except: # matplotlib_version = None # # info.Description = wordwrap(gui.aboutData.description + _("\n\nDevelopers:\n\t") + # "\n\t".join(gui.aboutData.developers) + # "\n\nAdditional credits:\n\t" + # "\n\t".join(gui.aboutData.credits) + # "\n\nLicenses:\n\t" + # "\n\t".join(gui.aboutData.licenses) + # "\n\nEVE Data: \t" + gamedata_version + # "\nPython: \t\t" + '{}.{}.{}'.format(v.major, v.minor, v.micro) + # "\nwxPython: \t" + wx.__version__ + # "\nSQLAlchemy: \t" + sqlalchemy.__version__ + # "\nmatplotlib: \t {}".format(matplotlib_version if matplotlib_version else "Not Installed"), # 500, wx.ClientDC(self)) # if "__WXGTK__" in wx.PlatformInfo: # forumUrl = "http://forums.eveonline.com/default.aspx?g=posts&t=466425" # else: # forumUrl = "http://forums.eveonline.com/default.aspx?g=posts&t=466425" # info.WebSite = (forumUrl, "pyfa thread at EVE Online forum") wx.adv.AboutBox(info) def OnShowGraphFrame(self, event): GraphFrame.openOne(self) def OnShowGraphFrameHidden(self, event): GraphFrame.openOne(self, includeHidden=True) def OnShowDevTools(self, event): DevTools.openOne(parent=self) def OnShowCharacterEditor(self, event): CharacterEditor.openOne(parent=self) def OnShowAttrEditor(self, event): AttributeEditor.openOne(parent=self) def OnShowTargetProfileEditor(self, event): TargetProfileEditor.openOne(parent=self) def OnShowDamagePatternEditor(self, event): DmgPatternEditor.openOne(parent=self) def OnShowImplantSetEditor(self, event): ImplantSetEditor.openOne(parent=self) def OnShowExportDialog(self, event): """ Export active fit """ sFit = Fit.getInstance() fit = sFit.getFit(self.getActiveFit()) defaultFile = "%s - %s.xml" % (fit.ship.item.name, fit.name) if fit else None with wx.FileDialog( self, _t("Save Fitting As..."), wildcard=_t("EVE XML fitting files") + " (*.xml)|*.xml", style=wx.FD_SAVE, defaultFile=defaultFile ) as dlg: if dlg.ShowModal() == wx.ID_OK: self.supress_left_up = True format_ = dlg.GetFilterIndex() path = dlg.GetPath() if format_ == 0: output = Port.exportXml([fit], None) if '.' not in os.path.basename(path): path += ".xml" with open(path, "w", encoding="utf-8") as openfile: openfile.write(output) openfile.close() else: pyfalog.warning("oops, invalid fit format %d" % format_) return def OnShowPreferenceDialog(self, event): with PreferenceDialog(self) as dlg: dlg.ShowModal() @staticmethod def goWiki(event): webbrowser.open('https://github.com/pyfa-org/Pyfa/wiki') @staticmethod def goForums(event): webbrowser.open('https://forums.eveonline.com/t/27156') def registerMenu(self): menuBar = self.GetMenuBar() # Quit self.Bind(wx.EVT_MENU, self.ExitApp, id=wx.ID_EXIT) # Widgets Inspector if config.debug: self.Bind(wx.EVT_MENU, self.openWXInspectTool, id=self.widgetInspectMenuID) self.Bind(wx.EVT_MENU, self.OnShowDevTools, id=menuBar.devToolsId) # About self.Bind(wx.EVT_MENU, self.ShowAboutBox, id=wx.ID_ABOUT) # Char editor self.Bind(wx.EVT_MENU, self.OnShowCharacterEditor, id=menuBar.characterEditorId) # Damage pattern editor self.Bind(wx.EVT_MENU, self.OnShowDamagePatternEditor, id=menuBar.damagePatternEditorId) # Target Profile editor self.Bind(wx.EVT_MENU, self.OnShowTargetProfileEditor, id=menuBar.targetProfileEditorId) # Implant Set editor self.Bind(wx.EVT_MENU, self.OnShowImplantSetEditor, id=menuBar.implantSetEditorId) # Import dialog self.Bind(wx.EVT_MENU, self.fileImportDialog, id=wx.ID_OPEN) # Export dialog self.Bind(wx.EVT_MENU, self.OnShowExportDialog, id=wx.ID_SAVEAS) # Import from Clipboard 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) # Copy skills needed self.Bind(wx.EVT_MENU, self.copySkillsNeeded, id=menuBar.copySkillsNeededId) # Import character self.Bind(wx.EVT_MENU, self.importCharacter, id=menuBar.importCharacterId) # Export HTML self.Bind(wx.EVT_MENU, self.exportHtml, id=menuBar.exportHtmlId) # Preference dialog self.Bind(wx.EVT_MENU, self.OnShowPreferenceDialog, id=wx.ID_PREFERENCES) # User guide self.Bind(wx.EVT_MENU, self.goWiki, id=menuBar.wikiId) self.Bind(wx.EVT_MENU, lambda evt: MainFrame.getInstance().command.Undo(), id=wx.ID_UNDO) self.Bind(wx.EVT_MENU, lambda evt: MainFrame.getInstance().command.Redo(), id=wx.ID_REDO) # EVE Forums self.Bind(wx.EVT_MENU, self.goForums, id=menuBar.forumId) # Save current character self.Bind(wx.EVT_MENU, self.saveChar, id=menuBar.saveCharId) # Save current character as another character self.Bind(wx.EVT_MENU, self.saveCharAs, id=menuBar.saveCharAsId) # Save current character self.Bind(wx.EVT_MENU, self.revertChar, id=menuBar.revertCharId) # Optimize fit price self.Bind(wx.EVT_MENU, self.optimizeFitPrice, id=menuBar.optimizeFitPrice) # Browse fittings self.Bind(wx.EVT_MENU, self.eveFittings, id=menuBar.eveFittingsId) # Export to EVE self.Bind(wx.EVT_MENU, self.exportToEve, id=menuBar.exportToEveId) # Handle SSO event (login/logout/manage characters, depending on mode and current state) self.Bind(wx.EVT_MENU, self.ssoHandler, id=menuBar.ssoLoginId) # Open attribute editor self.Bind(wx.EVT_MENU, self.OnShowAttrEditor, id=menuBar.attrEditorId) # Toggle Overrides self.Bind(wx.EVT_MENU, self.toggleOverrides, id=menuBar.toggleOverridesId) # Clipboard exports self.Bind(wx.EVT_MENU, self.exportToClipboard, id=wx.ID_COPY) # Fitting Restrictions self.Bind(wx.EVT_MENU, self.toggleIgnoreRestriction, id=menuBar.toggleIgnoreRestrictionID) # Graphs self.Bind(wx.EVT_MENU, self.OnShowGraphFrame, id=menuBar.graphFrameId) self.Bind(wx.EVT_MENU, self.OnShowGraphFrameHidden, id=self.hiddenGraphsId) toggleSearchBoxId = wx.NewId() toggleShipMarketId = wx.NewId() ctabnext = wx.NewId() ctabprev = wx.NewId() # Close Page self.Bind(wx.EVT_MENU, self.CloseCurrentPage, id=self.closePageId) self.Bind(wx.EVT_MENU, self.CloseAllPages, id=self.closeAllPagesId) self.Bind(wx.EVT_MENU, self.HAddPage, id=self.addPageId) self.Bind(wx.EVT_MENU, self.toggleSearchBox, id=toggleSearchBoxId) self.Bind(wx.EVT_MENU, self.toggleShipMarket, id=toggleShipMarketId) self.Bind(wx.EVT_MENU, self.CTabNext, id=ctabnext) self.Bind(wx.EVT_MENU, self.CTabPrev, id=ctabprev) actb = [(wx.ACCEL_CTRL, ord('T'), self.addPageId), (wx.ACCEL_CMD, ord('T'), self.addPageId), (wx.ACCEL_CTRL, ord('F'), toggleSearchBoxId), (wx.ACCEL_CMD, ord('F'), toggleSearchBoxId), (wx.ACCEL_CTRL, ord("W"), self.closePageId), (wx.ACCEL_CTRL, wx.WXK_F4, self.closePageId), (wx.ACCEL_CMD, ord("W"), self.closePageId), (wx.ACCEL_CTRL | wx.ACCEL_ALT, ord("G"), self.hiddenGraphsId), (wx.ACCEL_CMD | wx.ACCEL_ALT, ord("G"), self.hiddenGraphsId), (wx.ACCEL_CTRL | wx.ACCEL_ALT, ord("W"), self.closeAllPagesId), (wx.ACCEL_CTRL | wx.ACCEL_ALT, wx.WXK_F4, self.closeAllPagesId), (wx.ACCEL_CMD | wx.ACCEL_ALT, ord("W"), self.closeAllPagesId), (wx.ACCEL_CTRL, ord(" "), toggleShipMarketId), (wx.ACCEL_CMD, ord(" "), toggleShipMarketId), # Ctrl+(Shift+)Tab (wx.ACCEL_CTRL, wx.WXK_TAB, ctabnext), (wx.ACCEL_CTRL | wx.ACCEL_SHIFT, wx.WXK_TAB, ctabprev), (wx.ACCEL_CMD, wx.WXK_TAB, ctabnext), (wx.ACCEL_CMD | wx.ACCEL_SHIFT, wx.WXK_TAB, ctabprev), # Ctrl+Page(Up/Down) (wx.ACCEL_CTRL, wx.WXK_PAGEDOWN, ctabnext), (wx.ACCEL_CTRL, wx.WXK_PAGEUP, ctabprev), (wx.ACCEL_CMD, wx.WXK_PAGEDOWN, ctabnext), (wx.ACCEL_CMD, wx.WXK_PAGEUP, ctabprev), (wx.ACCEL_CMD | wx.ACCEL_SHIFT, ord("Z"), wx.ID_REDO) ] # Ctrl/Cmd+# for addition pane selection self.additionsSelect = [] for i in range(0, self.additionsPane.notebook.GetPageCount()): self.additionsSelect.append(wx.NewId()) self.Bind(wx.EVT_MENU, self.AdditionsTabSelect, id=self.additionsSelect[i]) actb.append((wx.ACCEL_CMD, i + 49, self.additionsSelect[i])) actb.append((wx.ACCEL_CTRL, i + 49, self.additionsSelect[i])) # Alt+1-9 for market item selection self.itemSelect = [] for i in range(0, 9): self.itemSelect.append(wx.NewId()) self.Bind(wx.EVT_MENU, self.ItemSelect, id=self.itemSelect[i]) actb.append((wx.ACCEL_ALT, i + 49, self.itemSelect[i])) atable = wx.AcceleratorTable(actb) self.SetAcceleratorTable(atable) def toggleIgnoreRestriction(self, event): sFit = Fit.getInstance() fitID = self.getActiveFit() fit = sFit.getFit(fitID) if not fit.ignoreRestrictions: with wx.MessageDialog( self, _t("Are you sure you wish to ignore fitting restrictions for the " "current fit? This could lead to wildly inaccurate results and possible errors."), _t("Confirm"), wx.YES_NO | wx.ICON_QUESTION ) as dlg: result = dlg.ShowModal() == wx.ID_YES else: with wx.MessageDialog( self, _t("Re-enabling fitting restrictions for this fit will also remove any illegal items " "from the fit. Do you want to continue?"), _t("Confirm"), wx.YES_NO | wx.ICON_QUESTION ) as dlg: result = dlg.ShowModal() == wx.ID_YES if result: self.command.Submit(cmd.GuiToggleFittingRestrictionsCommand(fitID=fitID)) def eveFittings(self, event): EveFittings.openOne(parent=self) def onSSOLogin(self, event): menu = self.GetMenuBar() menu.Enable(menu.eveFittingsId, True) menu.Enable(menu.exportToEveId, True) def updateEsiMenus(self, type): menu = self.GetMenuBar() sEsi = Esi.getInstance() menu.SetLabel(menu.ssoLoginId, _t("Manage Characters")) enable = len(sEsi.getSsoCharacters()) == 0 menu.Enable(menu.eveFittingsId, not enable) menu.Enable(menu.exportToEveId, not enable) def ssoHandler(self, event): SsoCharacterMgmt.openOne(parent=self) def exportToEve(self, event): ExportToEve.openOne(parent=self) def toggleOverrides(self, event): ModifiedAttributeDict.overrides_enabled = not ModifiedAttributeDict.overrides_enabled changedFitIDs = Fit.getInstance().processOverrideToggle() wx.PostEvent(self, GE.FitChanged(fitIDs=changedFitIDs)) menu = self.GetMenuBar() menu.SetLabel(menu.toggleOverridesId, _t("&Turn Overrides Off") if ModifiedAttributeDict.overrides_enabled else _t("&Turn Overrides On")) def saveChar(self, event): sChr = Character.getInstance() charID = self.charSelection.getActiveCharacter() sChr.saveCharacter(charID) wx.PostEvent(self, GE.CharListUpdated()) def saveCharAs(self, event): charID = self.charSelection.getActiveCharacter() CharacterEditor.SaveCharacterAs(self, charID) wx.PostEvent(self, GE.CharListUpdated()) def revertChar(self, event): sChr = Character.getInstance() charID = self.charSelection.getActiveCharacter() sChr.revertCharacter(charID) wx.PostEvent(self, GE.CharListUpdated()) def optimizeFitPrice(self, event): fitID = self.getActiveFit() sFit = Fit.getInstance() fit = sFit.getFit(fitID) if fit: def updateFitCb(replacementsCheaper): del self.waitDialog del self.disablerAll rebaseMap = {k.ID: v.ID for k, v in replacementsCheaper.items()} self.command.Submit(cmd.GuiRebaseItemsCommand(fitID=fitID, rebaseMap=rebaseMap)) fitItems = {i for i in Fit.fitItemIter(fit, forceFitImplants=True) if i is not fit.ship.item} self.disablerAll = wx.WindowDisabler() self.waitDialog = wx.BusyInfo(_t("Please Wait..."), parent=self) Price.getInstance().findCheaperReplacements(fitItems, updateFitCb, fetchTimeout=10) def AdditionsTabSelect(self, event): selTab = self.additionsSelect.index(event.GetId()) if selTab <= self.additionsPane.notebook.GetPageCount(): self.additionsPane.notebook.SetSelection(selTab) def ItemSelect(self, event): selItem = self.itemSelect.index(event.GetId()) activeListing = getattr(self.marketBrowser.itemView, 'active', None) if activeListing and selItem < len(activeListing): wx.PostEvent(self, ItemSelected(itemID=self.marketBrowser.itemView.active[selItem].ID, allowBatch=False)) def CTabNext(self, event): self.fitMultiSwitch.NextPage() def CTabPrev(self, event): self.fitMultiSwitch.PrevPage() def HAddPage(self, event): self.fitMultiSwitch.AddPage() def toggleShipMarket(self, event): sel = self.notebookBrowsers.GetSelection() self.notebookBrowsers.SetSelection(0 if sel == 1 else 1) def toggleSearchBox(self, event): sel = self.notebookBrowsers.GetSelection() if sel == 1: self.shipBrowser.navpanel.ToggleSearchBox() else: self.marketBrowser.search.Focus() def importFromClipboard(self, event): clipboard = fromClipboard() activeFit = self.getActiveFit() try: importType, importData = Port().importFitFromBuffer(clipboard, activeFit) if importType == "FittingItem": baseItem, mutaplasmidItem, mutations = importData[0] if mutaplasmidItem: if baseItem.isDrone: self.command.Submit(cmd.GuiImportLocalMutatedDroneCommand( activeFit, baseItem, mutaplasmidItem, mutations, amount=1)) else: self.command.Submit(cmd.GuiImportLocalMutatedModuleCommand( activeFit, baseItem, mutaplasmidItem, mutations)) else: self.command.Submit(cmd.GuiAddLocalModuleCommand(activeFit, baseItem.ID)) return if importType == "AdditionsDrones": if self.command.Submit(cmd.GuiImportLocalDronesCommand(activeFit, [(i.ID, a, m) for i, a, m in importData[0]])): self.additionsPane.select("Drones") return if importType == "AdditionsFighters": if self.command.Submit(cmd.GuiImportLocalFightersCommand(activeFit, [(i.ID, a, m) for i, a, m in importData[0]])): self.additionsPane.select("Fighters") return if importType == "AdditionsImplants": if self.command.Submit(cmd.GuiImportImplantsCommand(activeFit, [(i.ID, a, m) for i, a, m in importData[0]])): self.additionsPane.select("Implants") return if importType == "AdditionsBoosters": if self.command.Submit(cmd.GuiImportBoostersCommand(activeFit, [(i.ID, a, m) for i, a, m in importData[0]])): self.additionsPane.select("Boosters") return if importType == "AdditionsCargo": if self.command.Submit(cmd.GuiImportCargosCommand(activeFit, [(i.ID, a, m) for i, a, m in importData[0]])): self.additionsPane.select("Cargo") return except (KeyboardInterrupt, SystemExit): raise except: pyfalog.error("Attempt to import failed:\n{0}", clipboard) else: self._openAfterImport(importData) def exportToClipboard(self, event): with CopySelectDialog(self) as dlg: dlg.ShowModal() def exportSkillsNeeded(self, event): """ Exports skills needed for active fit and active character """ sCharacter = Character.getInstance() with wx.FileDialog( self, _t("Export Skills Needed As..."), wildcard=("|".join([ _t("EVEMon skills training file") + " (*.emp)|*.emp", _t("EVEMon skills training XML file") + " (*.xml)|*.xml", _t("Text skills training file") + " (*.txt)|*.txt" ])), style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, ) as dlg: if dlg.ShowModal() == wx.ID_OK: saveFmtInt = dlg.GetFilterIndex() if saveFmtInt == 0: # Per ordering of wildcards above saveFmt = "emp" elif saveFmtInt == 1: saveFmt = "xml" else: saveFmt = "txt" filePath = dlg.GetPath() if '.' not in os.path.basename(filePath): filePath += ".{0}".format(saveFmt) self.waitDialog = wx.BusyInfo(_t("Exporting skills needed..."), parent=self) sCharacter.backupSkills(filePath, saveFmt, self.getActiveFit(), self.closeWaitDialog) def copySkillsNeeded(self, event): """ Copies skills used by the fit that the character has to clipboard """ activeFitID = self.getActiveFit() if activeFitID is None: return sFit = Fit.getInstance() fit = sFit.getFit(activeFitID) if fit is None: return if not fit.calculated: fit.calculate() char = fit.character skillsMap = {} for thing in itertools.chain(fit.modules, fit.drones, fit.fighters, [fit.ship], fit.appliedImplants, fit.boosters, fit.cargo): self._collectAffectingSkills(thing, char, skillsMap) skillsList = "" for skillName in sorted(skillsMap): charLevel = skillsMap[skillName] for level in range(1, charLevel + 1): skillsList += "%s %d\n" % (skillName, level) toClipboard(skillsList) def _collectAffectingSkills(self, thing, char, skillsMap): """ Collect skills that affect items in the fit that the character has """ for attr in ("item", "charge"): if attr == "charge" and isinstance(thing, es_Fighter): continue subThing = getattr(thing, attr, None) if subThing is None: continue if isinstance(thing, es_Fighter) and attr == "charge": continue if attr == "charge": cont = getattr(thing, "chargeModifiedAttributes", None) else: cont = getattr(thing, "itemModifiedAttributes", None) if cont is not None: for attrName in cont.iterAfflictions(): for fit, afflictors in cont.getAfflictions(attrName).items(): for afflictor, operator, stackingGroup, preResAmount, postResAmount, used in afflictors: if isinstance(afflictor, Skill) and afflictor.character == char: skillName = afflictor.item.name if skillName not in skillsMap: skillsMap[skillName] = afflictor.level elif skillsMap[skillName] < afflictor.level: skillsMap[skillName] = afflictor.level def fileImportDialog(self, event): """Handles importing single/multiple EVE XML / EFT cfg fit files""" with wx.FileDialog( self, _t("Open One Or More Fitting Files"), wildcard=("|".join([ _t("EVE XML fitting files") + " (*.xml)|*.xml", _t("EFT text fitting files") + " (*.cfg)|*.cfg", _t("All Files") + "|*" ])), style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST | wx.FD_MULTIPLE ) as dlg: if dlg.ShowModal() == wx.ID_OK: # set some arbitrary spacing to create width in window progress = ProgressHelper(message=" " * 100, callback=self._openAfterImport) call = (Port.importFitsThreaded, [dlg.GetPaths(), progress], {}) self.handleProgress( title=_t("Importing fits"), style=wx.PD_CAN_ABORT | wx.PD_SMOOTH | wx.PD_APP_MODAL | wx.PD_AUTO_HIDE, call=call, progress=progress, errMsgLbl=_t("Import Error")) def backupToXml(self, event): """ Back up all fits to EVE XML file """ defaultFile = "pyfa-fits-%s.xml" % strftime("%Y%m%d_%H%M%S", gmtime()) with wx.FileDialog( self, _t("Save Backup As..."), wildcard=_t("EVE XML fitting file") + " (*.xml)|*.xml", style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, defaultFile=defaultFile) as fileDlg: if fileDlg.ShowModal() == wx.ID_OK: filePath = fileDlg.GetPath() if '.' not in os.path.basename(filePath): filePath += ".xml" fitAmount = Fit.getInstance().countAllFits() progress = ProgressHelper( message=_t("Backing up {} fits to: {}").format(fitAmount, filePath), maximum=fitAmount + 1) call = (Port.backupFits, [filePath, progress], {}) self.handleProgress( title=_t("Backup fits"), style=wx.PD_CAN_ABORT | wx.PD_SMOOTH | wx.PD_ELAPSED_TIME | wx.PD_APP_MODAL | wx.PD_AUTO_HIDE, call=call, progress=progress, errMsgLbl=_t("Export Error")) def exportHtml(self, event): from gui.utils.exportHtml import exportHtml sFit = Fit.getInstance() settings = HTMLExportSettings.getInstance() path = settings.getPath() if not os.path.isdir(os.path.dirname(path)): with wx.MessageDialog( self, _t("Invalid Path") + "\n\n" + _t("The following path is invalid or does not exist:") + f"\n{path}\n\n" + _t("Please verify path location pyfa's preferences."), _t("Error"), wx.OK | wx.ICON_ERROR ) as dlg: if dlg.ShowModal() == wx.ID_OK: return progress = ProgressHelper( message=_t("Generating HTML file at: {}").format(path), maximum=sFit.countAllFits() + 1) call = (exportHtml.getInstance().refreshFittingHtml, [True, progress], {}) self.handleProgress( title=_t("Backup fits"), style=wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME, call=call, progress=progress) def handleProgress(self, title, style, call, progress, errMsgLbl=None): extraArgs = {} if progress.maximum is not None: extraArgs['maximum'] = progress.maximum with wx.ProgressDialog( parent=self, title=title, message=progress.message, style=style, **extraArgs ) as dlg: func, args, kwargs = call func(*args, **kwargs) while progress.working: wx.MilliSleep(250) wx.Yield() (progress.dlgWorking, skip) = dlg.Update(progress.current, progress.message) if progress.error and errMsgLbl: with wx.MessageDialog( self, _t("The following error was generated") + f"\n\n{progress.error}\n\n" + _t("Be aware that already processed fits were not saved"), errMsgLbl, wx.OK | wx.ICON_ERROR ) as dlg: dlg.ShowModal() elif progress.callback: progress.callback(*progress.cbArgs) def _openAfterImport(self, fits): if len(fits) > 0: if len(fits) == 1: fit = fits[0] wx.PostEvent(self, FitSelected(fitID=fit.ID, from_import=True)) wx.PostEvent(self.shipBrowser, Stage3Selected(shipID=fit.shipID, back=True)) else: fits.sort(key=lambda _fit: (_fit.ship.item.name, _fit.name)) # Show 100 fits max fits = fits[:100] results = [] for fit in fits: results.append(( fit.ID, fit.name, fit.modifiedCoalesce, fit.ship.item, fit.notes )) wx.PostEvent(self.shipBrowser, ImportSelected(fits=results, back=True)) def importCharacter(self, event): """ Imports character XML file from EVE API """ with wx.FileDialog( self, _t("Open One Or More Character Files"), wildcard="|".join([ _t("EVE API XML character files") + " (*.xml)|*.xml", _t("All Files") + "|*" ]), style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST | wx.FD_MULTIPLE ) as dlg: if dlg.ShowModal() == wx.ID_OK: self.supress_left_up = True self.waitDialog = wx.BusyInfo(_t("Importing Character..."), parent=self) sCharacter = Character.getInstance() sCharacter.importCharacter(dlg.GetPaths(), self.importCharacterCallback) def importCharacterCallback(self): self.closeWaitDialog() wx.PostEvent(self, GE.CharListUpdated()) def closeWaitDialog(self): del self.waitDialog def openWXInspectTool(self, event): if not InspectionTool().initialized: InspectionTool().Init() # Find a widget to be selected in the tree. Use either the # one under the cursor, if any, or this frame. wnd, _ = wx.FindWindowAtPointer() if not wnd: wnd = self InspectionTool().Show(wnd, True)