Except for 1 (clipboardXML), same number of lines of code in mainFrame, lots of code gone from fit, and no more complicated. Also spotted an import reference that got missed.
909 lines
35 KiB
Python
909 lines
35 KiB
Python
#===============================================================================
|
|
# 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 <http://www.gnu.org/licenses/>.
|
|
#===============================================================================
|
|
|
|
import sys
|
|
import os.path
|
|
|
|
import sqlalchemy
|
|
import wx
|
|
import time
|
|
|
|
from codecs import open
|
|
|
|
from wx._core import PyDeadObjectError
|
|
from wx.lib.wordwrap import wordwrap
|
|
|
|
import config
|
|
import threading
|
|
import webbrowser
|
|
|
|
import gui.aboutData
|
|
import gui.chromeTabs
|
|
import gui.utils.animUtils as animUtils
|
|
import gui.globalEvents as GE
|
|
|
|
from gui.bitmapLoader import BitmapLoader
|
|
from gui.mainMenuBar import MainMenuBar
|
|
from gui.additionsPane import AdditionsPane
|
|
from gui.marketBrowser import MarketBrowser, ItemSelected
|
|
from gui.multiSwitch import MultiSwitch
|
|
from gui.statsPane import StatsPane
|
|
from gui.shipBrowser import ShipBrowser, FitSelected, ImportSelected, Stage3Selected
|
|
from gui.characterEditor import CharacterEditor, SaveCharacterAs
|
|
from gui.characterSelection import CharacterSelection
|
|
from gui.patternEditor import DmgPatternEditorDlg
|
|
from gui.resistsEditor import ResistsEditorDlg
|
|
from gui.setEditor import ImplantSetEditorDlg
|
|
from gui.preferenceDialog import PreferenceDialog
|
|
from gui.graphFrame import GraphFrame
|
|
from gui.copySelectDialog import CopySelectDialog
|
|
from gui.utils.clipboard import toClipboard, fromClipboard
|
|
from gui.fleetBrowser import FleetBrowser
|
|
from gui.updateDialog import UpdateDialog
|
|
from gui.builtinViews import *
|
|
from gui import graphFrame
|
|
|
|
from service.settings import SettingsProvider
|
|
from service.fit import Fit
|
|
from service.character import Character
|
|
from service.crest import Crest
|
|
from service.update import Update
|
|
|
|
# import this to access override setting
|
|
from eos.modifiedAttributeDict import ModifiedAttributeDict
|
|
from eos.db.saveddata.loadDefaultDatabaseValues import DefaultDatabaseValues
|
|
from eos import db
|
|
from service.port import Port
|
|
from service.settings import HTMLExportSettings
|
|
|
|
from time import gmtime, strftime
|
|
|
|
if not 'wxMac' in wx.PlatformInfo or ('wxMac' in wx.PlatformInfo and wx.VERSION >= (3,0)):
|
|
from service.crest import CrestModes
|
|
from gui.crestFittings import CrestFittings, ExportToEve, CrestMgmt
|
|
|
|
try:
|
|
from gui.propertyEditor import AttributeEditor
|
|
disableOverrideEditor = False
|
|
except ImportError, e:
|
|
print "Error loading Attribute Editor: %s.\nAccess to Attribute Editor is disabled."%e.message
|
|
disableOverrideEditor = True
|
|
|
|
#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.mainFrame = MainFrame.getInstance()
|
|
self.callback = callback
|
|
self.fits = fits
|
|
self.start()
|
|
|
|
def run(self):
|
|
time.sleep(0.5) # Give GUI some time to finish drawing
|
|
|
|
# `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]:
|
|
wx.PostEvent(self.mainFrame, FitSelected(fitID=fitID, startup=1))
|
|
|
|
wx.PostEvent(self.mainFrame, FitSelected(fitID=self.fits[-1], startup=2))
|
|
wx.CallAfter(self.callback)
|
|
|
|
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):
|
|
self.title=title
|
|
wx.Frame.__init__(self, None, wx.ID_ANY, self.title)
|
|
|
|
MainFrame.__instance = self
|
|
|
|
#Load stored settings (width/height/maximized..)
|
|
self.LoadMainFrameAttribs()
|
|
|
|
#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.IconFromBitmap(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.notebookBrowsers = gui.chromeTabs.PFNotebook(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, "Market", tabImage = marketImg, showClose = False)
|
|
self.marketBrowser.splitter.SetSashPosition(self.marketHeight)
|
|
|
|
self.shipBrowser = ShipBrowser(self.notebookBrowsers)
|
|
self.notebookBrowsers.AddPage(self.shipBrowser, "Fittings", tabImage = shipBrowserImg, showClose = False)
|
|
|
|
#=======================================================================
|
|
# DISABLED FOR RC2 RELEASE
|
|
#self.fleetBrowser = FleetBrowser(self.notebookBrowsers)
|
|
#self.notebookBrowsers.AddPage(self.fleetBrowser, "Fleets", showClose = 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)
|
|
|
|
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.widgetInspectMenuID = wx.NewId()
|
|
self.SetMenuBar(MainMenuBar())
|
|
self.registerMenu()
|
|
|
|
#Internal vars to keep track of other windows (graphing/stats)
|
|
self.graphFrame = None
|
|
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)
|
|
|
|
if not 'wxMac' in wx.PlatformInfo or ('wxMac' in wx.PlatformInfo and wx.VERSION >= (3,0)):
|
|
self.Bind(GE.EVT_SSO_LOGIN, self.onSSOLogin)
|
|
self.Bind(GE.EVT_SSO_LOGOUT, self.onSSOLogout)
|
|
|
|
self.titleTimer = wx.Timer(self)
|
|
self.Bind(wx.EVT_TIMER, self.updateTitle, self.titleTimer)
|
|
|
|
def ShowUpdateBox(self, release):
|
|
dlg = UpdateDialog(self, release)
|
|
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:
|
|
sFit.getFit(id, basic=True)
|
|
except:
|
|
fits.remove(id)
|
|
|
|
if not self.prevOpenFits['enabled'] or len(fits) is 0:
|
|
# add blank page if there are no fits to be loaded
|
|
self.fitMultiSwitch.AddPage()
|
|
return
|
|
|
|
self.waitDialog = wx.BusyInfo("Loading previous fits...")
|
|
OpenFitsThread(fits, self.closeWaitDialog)
|
|
|
|
def LoadMainFrameAttribs(self):
|
|
mainFrameDefaultAttribs = {"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)
|
|
|
|
if self.mainFrameAttribs["wnd_maximized"]:
|
|
width = mainFrameDefaultAttribs["wnd_width"]
|
|
height = mainFrameDefaultAttribs["wnd_height"]
|
|
self.Maximize()
|
|
else:
|
|
width = self.mainFrameAttribs["wnd_width"]
|
|
height = self.mainFrameAttribs["wnd_height"]
|
|
|
|
self.SetSize((width, height))
|
|
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
|
|
width,height = self.GetSize()
|
|
|
|
self.mainFrameAttribs["wnd_width"] = width
|
|
self.mainFrameAttribs["wnd_height"] = height
|
|
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):
|
|
sel = self.fitMultiSwitch.GetSelectedPage()
|
|
|
|
def CloseCurrentPage(self, evt):
|
|
ms = self.fitMultiSwitch
|
|
|
|
page = ms.GetSelection()
|
|
if page is not None:
|
|
ms.DeletePage(page)
|
|
|
|
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):
|
|
import eos.config
|
|
v = sys.version_info
|
|
info = wx.AboutDialogInfo()
|
|
info.Name = "pyfa"
|
|
info.Version = gui.aboutData.versionString
|
|
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" + eos.config.gamedata_version +
|
|
"\nPython: \t\t" + '{}.{}.{}'.format(v.major, v.minor, v.micro) +
|
|
"\nwxPython: \t" + wx.__version__ +
|
|
"\nSQLAlchemy: \t" + sqlalchemy.__version__,
|
|
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.AboutBox(info)
|
|
|
|
|
|
def showCharacterEditor(self, event):
|
|
dlg=CharacterEditor(self)
|
|
dlg.Show()
|
|
|
|
def showAttrEditor(self, event):
|
|
dlg=AttributeEditor(self)
|
|
dlg.Show()
|
|
|
|
def showTargetResistsEditor(self, event):
|
|
ResistsEditorDlg(self)
|
|
|
|
def showDamagePatternEditor(self, event):
|
|
dlg=DmgPatternEditorDlg(self)
|
|
dlg.ShowModal()
|
|
dlg.Destroy()
|
|
|
|
def showImplantSetEditor(self, event):
|
|
ImplantSetEditorDlg(self)
|
|
|
|
def showExportDialog(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
|
|
|
|
dlg = wx.FileDialog(self, "Save Fitting As...",
|
|
wildcard = "EVE XML fitting files (*.xml)|*.xml",
|
|
style = wx.FD_SAVE,
|
|
defaultFile=defaultFile)
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
format = dlg.GetFilterIndex()
|
|
path = dlg.GetPath()
|
|
if format == 0:
|
|
output = sFit.exportXml(None, self.getActiveFit())
|
|
if '.' not in os.path.basename(path):
|
|
path += ".xml"
|
|
else:
|
|
print "oops, invalid fit format %d" % format
|
|
dlg.Destroy()
|
|
return
|
|
file = open(path, "w", encoding="utf-8")
|
|
file.write(output)
|
|
file.close()
|
|
dlg.Destroy()
|
|
|
|
def showPreferenceDialog(self, event):
|
|
dlg = PreferenceDialog(self)
|
|
dlg.ShowModal()
|
|
|
|
def goWiki(self, event):
|
|
webbrowser.open('https://github.com/pyfa-org/Pyfa/wiki')
|
|
|
|
def goForums(self, event):
|
|
webbrowser.open('https://forums.eveonline.com/default.aspx?g=posts&t=466425')
|
|
|
|
def loadDatabaseDefaults(self, event):
|
|
# Import values that must exist otherwise Pyfa breaks
|
|
DefaultDatabaseValues.importRequiredDefaults()
|
|
# Import default values for damage profiles
|
|
DefaultDatabaseValues.importDamageProfileDefaults()
|
|
# Import default values for target resist profiles
|
|
DefaultDatabaseValues.importResistProfileDefaults()
|
|
|
|
def registerMenu(self):
|
|
menuBar = self.GetMenuBar()
|
|
# Quit
|
|
self.Bind(wx.EVT_MENU, self.ExitApp, id=wx.ID_EXIT)
|
|
# Load Default Database values
|
|
self.Bind(wx.EVT_MENU, self.loadDatabaseDefaults, id=menuBar.importDatabaseDefaultsId)
|
|
# Widgets Inspector
|
|
if config.debug:
|
|
self.Bind(wx.EVT_MENU, self.openWXInspectTool, id = self.widgetInspectMenuID)
|
|
# About
|
|
self.Bind(wx.EVT_MENU, self.ShowAboutBox, id=wx.ID_ABOUT)
|
|
# Char editor
|
|
self.Bind(wx.EVT_MENU, self.showCharacterEditor, id=menuBar.characterEditorId)
|
|
# Damage pattern editor
|
|
self.Bind(wx.EVT_MENU, self.showDamagePatternEditor, id=menuBar.damagePatternEditorId)
|
|
# Target Resists editor
|
|
self.Bind(wx.EVT_MENU, self.showTargetResistsEditor, id=menuBar.targetResistsEditorId)
|
|
# Implant Set editor
|
|
self.Bind(wx.EVT_MENU, self.showImplantSetEditor, id=menuBar.implantSetEditorId)
|
|
# Import dialog
|
|
self.Bind(wx.EVT_MENU, self.fileImportDialog, id=wx.ID_OPEN)
|
|
# Export dialog
|
|
self.Bind(wx.EVT_MENU, self.showExportDialog, 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)
|
|
# 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.showPreferenceDialog, id=wx.ID_PREFERENCES)
|
|
# User guide
|
|
self.Bind(wx.EVT_MENU, self.goWiki, id = menuBar.wikiId)
|
|
# 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)
|
|
|
|
# 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.showAttrEditor, 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)
|
|
|
|
#Graphs
|
|
self.Bind(wx.EVT_MENU, self.openGraphFrame, id=menuBar.graphFrameId)
|
|
|
|
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.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, 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)
|
|
]
|
|
|
|
# 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 eveFittings(self, event):
|
|
dlg=CrestFittings(self)
|
|
dlg.Show()
|
|
|
|
def updateTitle(self, event):
|
|
sCrest = Crest.getInstance()
|
|
char = sCrest.implicitCharacter
|
|
if char:
|
|
t = time.gmtime(char.eve.expires-time.time())
|
|
sTime = time.strftime("%H:%M:%S", t if t >= 0 else 0)
|
|
newTitle = "%s | %s - %s"%(self.title, char.name, sTime)
|
|
self.SetTitle(newTitle)
|
|
|
|
def onSSOLogin(self, event):
|
|
menu = self.GetMenuBar()
|
|
menu.Enable(menu.eveFittingsId, True)
|
|
menu.Enable(menu.exportToEveId, True)
|
|
|
|
if event.type == CrestModes.IMPLICIT:
|
|
menu.SetLabel(menu.ssoLoginId, "Logout Character")
|
|
self.titleTimer.Start(1000)
|
|
|
|
def onSSOLogout(self, event):
|
|
self.titleTimer.Stop()
|
|
self.SetTitle(self.title)
|
|
|
|
menu = self.GetMenuBar()
|
|
if event.type == CrestModes.IMPLICIT or event.numChars == 0:
|
|
menu.Enable(menu.eveFittingsId, False)
|
|
menu.Enable(menu.exportToEveId, False)
|
|
|
|
if event.type == CrestModes.IMPLICIT:
|
|
menu.SetLabel(menu.ssoLoginId, "Login to EVE")
|
|
|
|
def updateCrestMenus(self, type):
|
|
# in case we are logged in when switching, change title back
|
|
self.titleTimer.Stop()
|
|
self.SetTitle(self.title)
|
|
|
|
menu = self.GetMenuBar()
|
|
sCrest = Crest.getInstance()
|
|
|
|
if type == CrestModes.IMPLICIT:
|
|
menu.SetLabel(menu.ssoLoginId, "Login to EVE")
|
|
menu.Enable(menu.eveFittingsId, False)
|
|
menu.Enable(menu.exportToEveId, False)
|
|
else:
|
|
menu.SetLabel(menu.ssoLoginId, "Manage Characters")
|
|
enable = len(sCrest.getCrestCharacters()) == 0
|
|
menu.Enable(menu.eveFittingsId, not enable)
|
|
menu.Enable(menu.exportToEveId, not enable)
|
|
|
|
def ssoHandler(self, event):
|
|
sCrest = Crest.getInstance()
|
|
if sCrest.settings.get('mode') == CrestModes.IMPLICIT:
|
|
if sCrest.implicitCharacter is not None:
|
|
sCrest.logout()
|
|
else:
|
|
uri = sCrest.startServer()
|
|
webbrowser.open(uri)
|
|
else:
|
|
dlg=CrestMgmt(self)
|
|
dlg.Show()
|
|
|
|
def exportToEve(self, event):
|
|
dlg=ExportToEve(self)
|
|
dlg.Show()
|
|
|
|
def toggleOverrides(self, event):
|
|
ModifiedAttributeDict.OVERRIDES = not ModifiedAttributeDict.OVERRIDES
|
|
wx.PostEvent(self, GE.FitChanged(fitID=self.getActiveFit()))
|
|
menu = self.GetMenuBar()
|
|
menu.SetLabel(menu.toggleOverridesId, "Turn Overrides Off" if ModifiedAttributeDict.OVERRIDES else "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()
|
|
dlg = SaveCharacterAs(self, charID)
|
|
dlg.ShowModal()
|
|
|
|
def revertChar(self, event):
|
|
sChr = Character.getInstance()
|
|
charID = self.charSelection.getActiveCharacter()
|
|
sChr.revertCharacter(charID)
|
|
wx.PostEvent(self, GE.CharListUpdated())
|
|
|
|
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())
|
|
|
|
if selItem < len(self.marketBrowser.itemView.active):
|
|
wx.PostEvent(self, ItemSelected(itemID=self.marketBrowser.itemView.active[selItem].ID))
|
|
|
|
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 clipboardEft(self):
|
|
fit = db.getFit(self.getActiveFit())
|
|
toClipboard(Port.exportEft(fit))
|
|
|
|
def clipboardEftImps(self):
|
|
fit = db.getFit(self.getActiveFit())
|
|
toClipboard(Port.exportEftImps(fit))
|
|
|
|
def clipboardDna(self):
|
|
fit = db.getFit(self.getActiveFit())
|
|
toClipboard(Port.exportDna(fit))
|
|
|
|
def clipboardCrest(self):
|
|
fit = db.getFit(self.getActiveFit())
|
|
toClipboard(Port.exportCrest(fit))
|
|
|
|
def clipboardXml(self):
|
|
fitIDs = self.getActiveFit()
|
|
fits = map(lambda fitID: db.getFit(fitID), fitIDs)
|
|
toClipboard(Port.exportXml(None, *fits))
|
|
|
|
def clipboardMultiBuy(self):
|
|
fit = db.getFit(self.getActiveFit())
|
|
toClipboard(Port.exportMultiBuy(fit))
|
|
|
|
def importFromClipboard(self, event):
|
|
sFit = Fit.getInstance()
|
|
try:
|
|
fits = sFit.importFitFromBuffer(fromClipboard(), self.getActiveFit())
|
|
except:
|
|
pass
|
|
else:
|
|
self._openAfterImport(fits)
|
|
|
|
def exportToClipboard(self, event):
|
|
CopySelectDict = {CopySelectDialog.copyFormatEft: self.clipboardEft,
|
|
CopySelectDialog.copyFormatEftImps: self.clipboardEftImps,
|
|
CopySelectDialog.copyFormatXml: self.clipboardXml,
|
|
CopySelectDialog.copyFormatDna: self.clipboardDna,
|
|
CopySelectDialog.copyFormatCrest: self.clipboardCrest,
|
|
CopySelectDialog.copyFormatMultiBuy: self.clipboardMultiBuy}
|
|
dlg = CopySelectDialog(self)
|
|
dlg.ShowModal()
|
|
selected = dlg.GetSelected()
|
|
|
|
CopySelectDict[selected]()
|
|
|
|
|
|
dlg.Destroy()
|
|
|
|
def exportSkillsNeeded(self, event):
|
|
""" Exports skills needed for active fit and active character """
|
|
sCharacter = 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()
|
|
|
|
if saveFmtInt == 0: # Per ordering of wildcards above
|
|
saveFmt = "emp"
|
|
elif saveFmtInt == 1:
|
|
saveFmt = "xml"
|
|
else:
|
|
saveFmt = "txt"
|
|
|
|
filePath = saveDialog.GetPath()
|
|
if '.' not in os.path.basename(filePath):
|
|
filePath += ".{0}".format(saveFmt)
|
|
|
|
self.waitDialog = wx.BusyInfo("Exporting skills needed...")
|
|
sCharacter.backupSkills(filePath, saveFmt, self.getActiveFit(), self.closeWaitDialog)
|
|
|
|
saveDialog.Destroy()
|
|
|
|
def fileImportDialog(self, event):
|
|
"""Handles importing single/multiple EVE XML / EFT cfg fit files"""
|
|
sFit = Fit.getInstance()
|
|
dlg = wx.FileDialog(self, "Open One Or More Fitting Files",
|
|
wildcard = "EVE XML fitting files (*.xml)|*.xml|" \
|
|
"EFT text fitting files (*.cfg)|*.cfg|" \
|
|
"All Files (*)|*",
|
|
style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST | wx.FD_MULTIPLE)
|
|
if (dlg.ShowModal() == wx.ID_OK):
|
|
self.progressDialog = wx.ProgressDialog(
|
|
"Importing fits",
|
|
" "*100, # set some arbitrary spacing to create width in window
|
|
parent=self, style = wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME)
|
|
self.progressDialog.message = None
|
|
sFit.importFitsThreaded(dlg.GetPaths(), self.fileImportCallback)
|
|
self.progressDialog.ShowModal()
|
|
dlg.Destroy()
|
|
|
|
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())
|
|
|
|
saveDialog = wx.FileDialog(self, "Save Backup As...",
|
|
wildcard = "EVE XML fitting file (*.xml)|*.xml",
|
|
style = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT,
|
|
defaultFile=defaultFile)
|
|
|
|
if saveDialog.ShowModal() == wx.ID_OK:
|
|
filePath = saveDialog.GetPath()
|
|
if '.' not in os.path.basename(filePath):
|
|
filePath += ".xml"
|
|
|
|
sFit = Fit.getInstance()
|
|
max = sFit.countAllFits()
|
|
|
|
self.progressDialog = wx.ProgressDialog("Backup fits",
|
|
"Backing up %d fits to: %s"%(max, filePath),
|
|
maximum=max, parent=self,
|
|
style=wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME)
|
|
sFit.backupFits(filePath, self.backupCallback)
|
|
self.progressDialog.ShowModal()
|
|
|
|
def exportHtml(self, event):
|
|
from gui.utils.exportHtml import exportHtml
|
|
sFit = Fit.getInstance()
|
|
settings = HTMLExportSettings.getInstance()
|
|
|
|
max = sFit.countAllFits()
|
|
path = settings.getPath()
|
|
|
|
if not os.path.isdir(os.path.dirname(path)):
|
|
dlg = wx.MessageDialog(self,
|
|
"Invalid Path\n\nThe following path is invalid or does not exist: \n%s\n\nPlease verify path location pyfa's preferences."%path,
|
|
"Error", wx.OK | wx.ICON_ERROR)
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
return
|
|
|
|
self.progressDialog = wx.ProgressDialog("Backup fits",
|
|
"Generating HTML file at: %s"%path,
|
|
maximum=max, parent=self,
|
|
style=wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME)
|
|
|
|
exportHtml.getInstance().refreshFittingHtml(True, self.backupCallback)
|
|
self.progressDialog.ShowModal()
|
|
|
|
def backupCallback(self, info):
|
|
if info == -1:
|
|
self.closeProgressDialog()
|
|
else:
|
|
self.progressDialog.Update(info)
|
|
|
|
def fileImportCallback(self, action, data=None):
|
|
"""
|
|
While importing fits from file, the logic calls back to this function to
|
|
update progress bar to show activity. XML files can contain multiple
|
|
ships with multiple fits, whereas EFT cfg files contain many fits of
|
|
a single ship. When iterating through the files, we update the message
|
|
when we start a new file, and then Pulse the progress bar with every fit
|
|
that is processed.
|
|
|
|
action : a flag that lets us know how to deal with :data
|
|
None: Pulse the progress bar
|
|
1: Replace message with data
|
|
other: Close dialog and handle based on :action (-1 open fits, -2 display error)
|
|
"""
|
|
|
|
if action is None:
|
|
self.progressDialog.Pulse()
|
|
elif action == 1 and data != self.progressDialog.message:
|
|
self.progressDialog.message = data
|
|
self.progressDialog.Pulse(data)
|
|
else:
|
|
self.closeProgressDialog()
|
|
if action == -1:
|
|
self._openAfterImport(data)
|
|
elif action == -2:
|
|
dlg = wx.MessageDialog(self,
|
|
"The following error was generated\n\n%s\n\nBe aware that already processed fits were not saved"%data,
|
|
"Import Error", wx.OK | wx.ICON_ERROR)
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
return
|
|
|
|
def _openAfterImport(self, fits):
|
|
if len(fits) > 0:
|
|
if len(fits) == 1:
|
|
fit = fits[0]
|
|
wx.PostEvent(self, FitSelected(fitID=fit.ID))
|
|
wx.PostEvent(self.shipBrowser, Stage3Selected(shipID=fit.shipID, back=True))
|
|
else:
|
|
wx.PostEvent(self.shipBrowser, ImportSelected(fits=fits, back=True))
|
|
|
|
def closeProgressDialog(self):
|
|
# Windows apparently handles ProgressDialogs differently. We can
|
|
# simply Destroy it here, but for other platforms we must Close it
|
|
if 'wxMSW' in wx.PlatformInfo:
|
|
self.progressDialog.Destroy()
|
|
else:
|
|
self.progressDialog.EndModal(wx.ID_OK)
|
|
self.progressDialog.Close()
|
|
|
|
def importCharacter(self, event):
|
|
""" Imports character XML file from EVE API """
|
|
dlg = wx.FileDialog(self, "Open One Or More Character Files",
|
|
wildcard="EVE API XML character 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 = wx.BusyInfo("Importing Character...")
|
|
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 openGraphFrame(self, event):
|
|
if not self.graphFrame:
|
|
self.graphFrame = GraphFrame(self)
|
|
|
|
if graphFrame.enabled:
|
|
self.graphFrame.Show()
|
|
else:
|
|
self.graphFrame.SetFocus()
|
|
|
|
def openWXInspectTool(self, event):
|
|
from wx.lib.inspection import InspectionTool
|
|
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)
|
|
|