From be53dedb189cf573415d9a6643ab3afd16cb15cb Mon Sep 17 00:00:00 2001 From: Ebag333 Date: Tue, 13 Dec 2016 21:23:01 -0800 Subject: [PATCH] Large pep8 compliance to make work for Tox --- gui/PFListPane.py | 3 +- gui/PFSearchBox.py | 5 +- gui/aboutData.py | 1 - gui/additionsPane.py | 1 - gui/bitmapLoader.py | 1 - gui/cargoView.py | 24 +- gui/characterEditor.py | 80 +-- gui/chromeTabs.py | 30 +- gui/contextMenu.py | 359 +++++++------ gui/copySelectDialog.py | 6 +- gui/crestFittings.py | 106 ++-- gui/display.py | 7 +- gui/fighterView.py | 53 +- gui/fleetBrowser.py | 897 +++++++++++++++---------------- gui/gangView.py | 16 +- gui/globalEvents.py | 1 - gui/graphFrame.py | 563 +++++++++---------- gui/itemStats.py | 45 +- gui/mainFrame.py | 13 +- gui/mainMenuBar.py | 4 +- gui/marketBrowser.py | 923 ++++++++++++++++---------------- gui/patternEditor.py | 28 +- gui/propertyEditor.py | 12 +- gui/pyfatogglepanel.py | 3 +- gui/resistsEditor.py | 4 +- gui/setEditor.py | 18 +- gui/sfBrowserItem.py | 3 +- gui/shipBrowser.py | 50 +- gui/statsPane.py | 3 +- gui/updateDialog.py | 98 ++-- gui/utils/fonts.py | 2 - service/attribute.py | 2 + service/character.py | 11 +- service/crest.py | 25 +- service/damagePattern.py | 4 +- service/eveapi.py | 15 +- service/fit.py | 31 +- service/fleet.py | 435 +++++++-------- service/implantSet.py | 2 + service/market.py | 185 ++++--- service/network.py | 15 +- service/port.py | 6 +- service/prefetch.py | 142 ++--- service/pycrest/eve.py | 1 - service/pycrest/weak_ciphers.py | 6 +- service/server.py | 1 - service/settings.py | 27 +- service/targetResists.py | 4 +- service/update.py | 9 +- tox.ini | 2 +- 50 files changed, 2216 insertions(+), 2066 deletions(-) diff --git a/gui/PFListPane.py b/gui/PFListPane.py index 9803f6873..556b97638 100644 --- a/gui/PFListPane.py +++ b/gui/PFListPane.py @@ -22,7 +22,8 @@ import wx class PFListPane(wx.ScrolledWindow): def __init__(self, parent): - wx.ScrolledWindow.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.Size(1, 1), style=wx.TAB_TRAVERSAL) + wx.ScrolledWindow.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.Size(1, 1), + style=wx.TAB_TRAVERSAL) self._wList = [] self._wCount = 0 diff --git a/gui/PFSearchBox.py b/gui/PFSearchBox.py index 452bdbf09..ece5cef5f 100644 --- a/gui/PFSearchBox.py +++ b/gui/PFSearchBox.py @@ -2,7 +2,6 @@ import wx import gui.utils.colorUtils as colorUtils import gui.utils.drawUtils as drawUtils - SearchButton, EVT_SEARCH_BTN = wx.lib.newevent.NewEvent() CancelButton, EVT_CANCEL_BTN = wx.lib.newevent.NewEvent() TextEnter, EVT_TEXT_ENTER = wx.lib.newevent.NewEvent() @@ -40,7 +39,9 @@ class PFSearchBox(wx.Window): self._hl = False w, h = size - self.EditBox = wx.TextCtrl(self, wx.ID_ANY, "", wx.DefaultPosition, (-1, h - 2 if 'wxGTK' in wx.PlatformInfo else -1), wx.TE_PROCESS_ENTER | (wx.BORDER_NONE if 'wxGTK' in wx.PlatformInfo else 0)) + self.EditBox = wx.TextCtrl(self, wx.ID_ANY, "", wx.DefaultPosition, + (-1, h - 2 if 'wxGTK' in wx.PlatformInfo else -1), + wx.TE_PROCESS_ENTER | (wx.BORDER_NONE if 'wxGTK' in wx.PlatformInfo else 0)) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBk) diff --git a/gui/aboutData.py b/gui/aboutData.py index 98e4c12f7..6e8ce07bc 100644 --- a/gui/aboutData.py +++ b/gui/aboutData.py @@ -19,7 +19,6 @@ import config - versionString = "{0} {1} - {2} {3}".format(config.version, config.tag, config.expansionName, config.expansionVersion) licenses = ( "pyfa is released under GNU GPLv3 - see included LICENSE file", diff --git a/gui/additionsPane.py b/gui/additionsPane.py index 2e7e459e3..c0616f597 100644 --- a/gui/additionsPane.py +++ b/gui/additionsPane.py @@ -35,7 +35,6 @@ import gui.chromeTabs class AdditionsPane(TogglePanel): - def __init__(self, parent): TogglePanel.__init__(self, parent, forceLayout=1) diff --git a/gui/bitmapLoader.py b/gui/bitmapLoader.py index fb8be8a02..307a11267 100644 --- a/gui/bitmapLoader.py +++ b/gui/bitmapLoader.py @@ -30,7 +30,6 @@ except ImportError: class BitmapLoader(object): - try: archive = zipfile.ZipFile(os.path.join(config.pyfaPath, 'imgs.zip'), 'r') except IOError: diff --git a/gui/cargoView.py b/gui/cargoView.py index 527b41691..6fe9e3c9e 100644 --- a/gui/cargoView.py +++ b/gui/cargoView.py @@ -27,21 +27,21 @@ from service.market import Market class CargoViewDrop(wx.PyDropTarget): - def __init__(self, dropFn): - wx.PyDropTarget.__init__(self) - self.dropFn = dropFn - # this is really transferring an EVE itemID - self.dropData = wx.PyTextDataObject() - self.SetDataObject(self.dropData) + def __init__(self, dropFn): + wx.PyDropTarget.__init__(self) + self.dropFn = dropFn + # this is really transferring an EVE itemID + self.dropData = wx.PyTextDataObject() + self.SetDataObject(self.dropData) - def OnData(self, x, y, t): - if self.GetData(): - data = self.dropData.GetText().split(':') - self.dropFn(x, y, data) - return t + def OnData(self, x, y, t): + if self.GetData(): + data = self.dropData.GetText().split(':') + self.dropFn(x, y, data) + return t -# @todo: Was copied form another class and modified. Look through entire file, refine +# @todo: Was copied form another class and modified. Look through entire file, refine class CargoView(d.Display): DEFAULT_COLS = ["Base Icon", "Base Name", diff --git a/gui/characterEditor.py b/gui/characterEditor.py index e9d512a74..2d9e63277 100644 --- a/gui/characterEditor.py +++ b/gui/characterEditor.py @@ -99,14 +99,14 @@ class CharacterEntityEditor(EntityEditor): class CharacterEditor(wx.Frame): def __init__(self, parent): - wx.Frame.__init__ (self, parent, id=wx.ID_ANY, title=u"pyfa: Character Editor", pos=wx.DefaultPosition, - size=wx.Size(640, 600), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER) + wx.Frame.__init__(self, parent, id=wx.ID_ANY, title=u"pyfa: Character Editor", pos=wx.DefaultPosition, + size=wx.Size(640, 600), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER) i = wx.IconFromBitmap(BitmapLoader.getBitmap("character_small", "gui")) self.SetIcon(i) self.mainFrame = parent - #self.disableWin = wx.WindowDisabler(self) + # self.disableWin = wx.WindowDisabler(self) sFit = Fit.getInstance() self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)) @@ -179,7 +179,7 @@ class CharacterEditor(wx.Frame): event.Skip() def editingFinished(self, event): - #del self.disableWin + # del self.disableWin wx.PostEvent(self.mainFrame, GE.CharListUpdated()) self.Destroy() @@ -201,7 +201,7 @@ class CharacterEditor(wx.Frame): wx.PostEvent(self, GE.CharListUpdated()) def closeEvent(self, event): - #del self.disableWin + # del self.disableWin wx.PostEvent(self.mainFrame, GE.CharListUpdated()) self.Destroy() @@ -234,18 +234,20 @@ class CharacterEditor(wx.Frame): wx.Frame.Destroy(self) -class SkillTreeView (wx.Panel): + +class SkillTreeView(wx.Panel): def __init__(self, parent): - wx.Panel.__init__ (self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.TAB_TRAVERSAL) + wx.Panel.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, + style=wx.TAB_TRAVERSAL) self.charEditor = self.Parent.Parent # first parent is Notebook, second is Character Editor self.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) pmainSizer = wx.BoxSizer(wx.VERTICAL) - tree = self.skillTreeListCtrl = wx.gizmos.TreeListCtrl(self, wx.ID_ANY, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT) + tree = self.skillTreeListCtrl = wx.gizmos.TreeListCtrl(self, wx.ID_ANY, + style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT) pmainSizer.Add(tree, 1, wx.EXPAND | wx.ALL, 5) - self.imageList = wx.ImageList(16, 16) tree.SetImageList(self.imageList) self.skillBookImageId = self.imageList.Add(BitmapLoader.getBitmap("skill_small", "gui")) @@ -289,7 +291,6 @@ class SkillTreeView (wx.Panel): self.revertID = wx.NewId() self.levelChangeMenu.Append(self.revertID, "Revert") - self.saveID = wx.NewId() self.levelChangeMenu.Append(self.saveID, "Save") @@ -328,7 +329,7 @@ class SkillTreeView (wx.Panel): if tree.GetItemText(child) == "dummy": tree.Delete(child) - #Get the real intrestin' stuff + # Get the real intrestin' stuff sChar = Character.getInstance() char = self.charEditor.entityEditor.getActiveEntity() for id, name in sChar.getSkills(tree.GetPyData(root)): @@ -391,7 +392,7 @@ class SkillTreeView (wx.Panel): class ImplantEditorView(BaseImplantEditorView): def __init__(self, parent): - BaseImplantEditorView.__init__ (self, parent) + BaseImplantEditorView.__init__(self, parent) self.determineEnabled() @@ -445,9 +446,10 @@ class ImplantEditorView(BaseImplantEditorView): self.Enable() -class APIView (wx.Panel): +class APIView(wx.Panel): def __init__(self, parent): - wx.Panel.__init__ (self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.Size(500, 300), style=wx.TAB_TRAVERSAL) + wx.Panel.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.Size(500, 300), + style=wx.TAB_TRAVERSAL) self.charEditor = self.Parent.Parent # first parent is Notebook, second is Character Editor self.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) @@ -456,17 +458,17 @@ class APIView (wx.Panel): pmainSizer = wx.BoxSizer(wx.VERTICAL) - hintSizer = wx.BoxSizer( wx.HORIZONTAL ) + hintSizer = wx.BoxSizer(wx.HORIZONTAL) hintSizer.AddStretchSpacer() - self.stDisabledTip = wx.StaticText( self, wx.ID_ANY, u"You cannot add API Details for All 0 and All 5 characters.\n" - u"Please select another character or make a new one.", style=wx.ALIGN_CENTER ) - self.stDisabledTip.Wrap( -1 ) - hintSizer.Add( self.stDisabledTip, 0, wx.TOP | wx.BOTTOM, 10 ) + self.stDisabledTip = wx.StaticText(self, wx.ID_ANY, + u"You cannot add API Details for All 0 and All 5 characters.\n" + u"Please select another character or make a new one.", style=wx.ALIGN_CENTER) + self.stDisabledTip.Wrap(-1) + hintSizer.Add(self.stDisabledTip, 0, wx.TOP | wx.BOTTOM, 10) self.stDisabledTip.Hide() hintSizer.AddStretchSpacer() pmainSizer.Add(hintSizer, 0, wx.EXPAND, 5) - fgSizerInput = wx.FlexGridSizer(3, 2, 0, 0) fgSizerInput.AddGrowableCol(1) fgSizerInput.SetFlexibleDirection(wx.BOTH) @@ -498,39 +500,44 @@ class APIView (wx.Panel): pmainSizer.Add(fgSizerInput, 0, wx.EXPAND, 5) - btnSizer = wx.BoxSizer( wx.HORIZONTAL ) + btnSizer = wx.BoxSizer(wx.HORIZONTAL) btnSizer.AddStretchSpacer() self.btnFetchCharList = wx.Button(self, wx.ID_ANY, u"Get Characters") btnSizer.Add(self.btnFetchCharList, 0, wx.ALL, 2) self.btnFetchCharList.Bind(wx.EVT_BUTTON, self.fetchCharList) - self.btnFetchSkills = wx.Button(self, wx.ID_ANY, u"Fetch Skills") - btnSizer.Add(self.btnFetchSkills, 0, wx.ALL, 2) + self.btnFetchSkills = wx.Button(self, wx.ID_ANY, u"Fetch Skills") + btnSizer.Add(self.btnFetchSkills, 0, wx.ALL, 2) self.btnFetchSkills.Bind(wx.EVT_BUTTON, self.fetchSkills) self.btnFetchSkills.Enable(False) btnSizer.AddStretchSpacer() pmainSizer.Add(btnSizer, 0, wx.EXPAND, 5) - self.stStatus = wx.StaticText(self, wx.ID_ANY, wx.EmptyString) + self.stStatus = wx.StaticText(self, wx.ID_ANY, wx.EmptyString) pmainSizer.Add(self.stStatus, 0, wx.ALL, 5) pmainSizer.AddStretchSpacer() - self.stAPITip = wx.StaticText( self, wx.ID_ANY, u"You can create a pre-defined key here (only CharacterSheet is required):", wx.DefaultPosition, wx.DefaultSize, 0 ) - self.stAPITip.Wrap( -1 ) + self.stAPITip = wx.StaticText(self, wx.ID_ANY, + u"You can create a pre-defined key here (only CharacterSheet is required):", + wx.DefaultPosition, wx.DefaultSize, 0) + self.stAPITip.Wrap(-1) - pmainSizer.Add( self.stAPITip, 0, wx.ALL, 2 ) + pmainSizer.Add(self.stAPITip, 0, wx.ALL, 2) - self.hlEveAPI = wx.HyperlinkCtrl( self, wx.ID_ANY, self.apiUrlCreatePredefined, self.apiUrlCreatePredefined, wx.DefaultPosition, wx.DefaultSize, wx.HL_DEFAULT_STYLE ) - pmainSizer.Add( self.hlEveAPI, 0, wx.ALL, 2 ) + self.hlEveAPI = wx.HyperlinkCtrl(self, wx.ID_ANY, self.apiUrlCreatePredefined, self.apiUrlCreatePredefined, + wx.DefaultPosition, wx.DefaultSize, wx.HL_DEFAULT_STYLE) + pmainSizer.Add(self.hlEveAPI, 0, wx.ALL, 2) - self.stAPITip2 = wx.StaticText( self, wx.ID_ANY, u"Or, you can choose an existing key from:", wx.DefaultPosition, wx.DefaultSize, 0 ) - self.stAPITip2.Wrap( -1 ) - pmainSizer.Add( self.stAPITip2, 0, wx.ALL, 2 ) + self.stAPITip2 = wx.StaticText(self, wx.ID_ANY, u"Or, you can choose an existing key from:", wx.DefaultPosition, + wx.DefaultSize, 0) + self.stAPITip2.Wrap(-1) + pmainSizer.Add(self.stAPITip2, 0, wx.ALL, 2) - self.hlEveAPI2 = wx.HyperlinkCtrl( self, wx.ID_ANY, self.apiUrlKeyList, self.apiUrlKeyList, wx.DefaultPosition, wx.DefaultSize, wx.HL_DEFAULT_STYLE ) - pmainSizer.Add( self.hlEveAPI2, 0, wx.ALL, 2 ) + self.hlEveAPI2 = wx.HyperlinkCtrl(self, wx.ID_ANY, self.apiUrlKeyList, self.apiUrlKeyList, wx.DefaultPosition, + wx.DefaultSize, wx.HL_DEFAULT_STYLE) + pmainSizer.Add(self.hlEveAPI2, 0, wx.ALL, 2) self.charEditor.entityEditor.Bind(wx.EVT_CHOICE, self.charChanged) @@ -587,7 +594,7 @@ class APIView (wx.Panel): except TimeoutError, e: self.stStatus.SetLabel("Request timed out. Please check network connectivity and/or proxy settings.") except Exception, e: - self.stStatus.SetLabel("Error:\n%s"%e.message) + self.stStatus.SetLabel("Error:\n%s" % e.message) else: self.charChoice.Clear() for charName in list: @@ -611,8 +618,8 @@ class APIView (wx.Panel): except Exception, e: self.stStatus.SetLabel("Unable to retrieve %s\'s skills. Error message:\n%s" % (charName, e)) -class SaveCharacterAs(wx.Dialog): +class SaveCharacterAs(wx.Dialog): def __init__(self, parent, charID): wx.Dialog.__init__(self, parent, title="Save Character As...", size=wx.Size(300, 60)) self.charID = charID @@ -640,4 +647,3 @@ class SaveCharacterAs(wx.Dialog): event.Skip() self.Close() - \ No newline at end of file diff --git a/gui/chromeTabs.py b/gui/chromeTabs.py index be2342622..00998eb87 100644 --- a/gui/chromeTabs.py +++ b/gui/chromeTabs.py @@ -26,7 +26,6 @@ from gui.bitmapLoader import BitmapLoader from service.fit import Fit - _PageChanging, EVT_NOTEBOOK_PAGE_CHANGING = wx.lib.newevent.NewEvent() _PageChanged, EVT_NOTEBOOK_PAGE_CHANGED = wx.lib.newevent.NewEvent() _PageAdding, EVT_NOTEBOOK_PAGE_ADDING = wx.lib.newevent.NewEvent() @@ -527,7 +526,10 @@ class PFTabRenderer: """ self.tabRegion = wx.RegionFromBitmap(self.tabBackBitmap) self.closeBtnRegion = wx.RegionFromBitmap(self.ctabCloseBmp) - self.closeBtnRegion.Offset(self.contentWidth + self.leftWidth - self.ctabCloseBmp.GetWidth() / 2, (self.tabHeight - self.ctabCloseBmp.GetHeight()) / 2) + self.closeBtnRegion.Offset( + self.contentWidth + self.leftWidth - self.ctabCloseBmp.GetWidth() / 2, + (self.tabHeight - self.ctabCloseBmp.GetHeight()) / 2 + ) def InitColors(self): """Determines colors used for tab, based on system settings""" @@ -1277,7 +1279,12 @@ class PFTabsContainer(wx.Panel): selpos = pos if selected is not skipTab: selected.SetPosition((selpos, self.containerHeight - self.height)) - self.addButton.SetPosition((round(tabsWidth) + self.inclination * 2, self.containerHeight - self.height / 2 - self.addButton.GetHeight() / 3)) + self.addButton.SetPosition( + ( + round(tabsWidth) + self.inclination * 2, + self.containerHeight - self.height / 2 - self.addButton.GetHeight() / 3 + ) + ) def OnLeaveWindow(self, event): @@ -1302,7 +1309,12 @@ class PFTabsContainer(wx.Panel): if not self.previewTab.GetSelected(): page = self.Parent.GetPage(self.GetTabIndex(self.previewTab)) if page.Snapshot(): - self.previewWnd = PFNotebookPagePreview(self, (mposx + 3, mposy + 3), page.Snapshot(), self.previewTab.text) + self.previewWnd = PFNotebookPagePreview( + self, + (mposx + 3, mposy + 3), + page.Snapshot(), + self.previewTab.text + ) self.previewWnd.Show() event.Skip() @@ -1310,7 +1322,15 @@ class PFTabsContainer(wx.Panel): class PFNotebookPagePreview(wx.Frame): def __init__(self, parent, pos, bitmap, title): - wx.Frame.__init__(self, parent, id=wx.ID_ANY, title=wx.EmptyString, pos=pos, size=wx.DefaultSize, style=wx.NO_BORDER | wx.FRAME_NO_TASKBAR | wx.STAY_ON_TOP) + wx.Frame.__init__( + self, + parent, + id=wx.ID_ANY, + title=wx.EmptyString, + pos=pos, + size=wx.DefaultSize, + style=wx.NO_BORDER | wx.FRAME_NO_TASKBAR | wx.STAY_ON_TOP + ) self.title = title self.bitmap = bitmap diff --git a/gui/contextMenu.py b/gui/contextMenu.py index 2a8033b44..5e8839cac 100644 --- a/gui/contextMenu.py +++ b/gui/contextMenu.py @@ -1,180 +1,179 @@ -# ============================================================================= -# 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 wx -import logging - - -logger = logging.getLogger(__name__) - - -class ContextMenu(object): - menus = [] - _ids = [] # [wx.NewId() for x in xrange(200)] # init with decent amount - _idxid = -1 - - @classmethod - def register(cls): - ContextMenu.menus.append(cls) - - @classmethod - def getMenu(cls, selection, *fullContexts): - """ - getMenu returns a menu that is used with wx.PopupMenu. - - selection: provides a list of what was selected. If only 1 item was - selected, it's is a 1-item list or tuple. Can also be None for - contexts without selection, such as statsPane or projected view - fullContexts: a number of tuples of the following tuple: - srcContext - context were menu was spawned, eg: projectedFit, - cargoItem, etc - itemContext - usually the name of the item's category - - eg: - (('fittingModule', 'Module'), ('fittingShip', 'Ship')) - (('marketItemGroup', 'Implant'),) - (('fittingShip', 'Ship'),) - """ - cls._idxid = -1 - debug_start = len(cls._ids) - - rootMenu = wx.Menu() - rootMenu.info = {} - rootMenu.selection = (selection,) if not hasattr(selection, "__iter__") else selection - empty = True - for i, fullContext in enumerate(fullContexts): - amount = 0 - srcContext = fullContext[0] - try: - itemContext = fullContext[1] - except IndexError: - itemContext = None - for menuHandler in cls.menus: - # loop through registered menus - m = menuHandler() - if m.display(srcContext, selection): - amount += 1 - texts = m.getText(itemContext, selection) - if isinstance(texts, basestring): - texts = (texts,) - - bitmap = m.getBitmap(srcContext, selection) - multiple = not isinstance(bitmap, wx.Bitmap) - for it, text in enumerate(texts): - id = cls.nextID() - rootItem = wx.MenuItem(rootMenu, id, text) - rootMenu.info[id] = (m, fullContext, it) - - sub = m.getSubMenu(srcContext, selection, rootMenu, it, rootItem) - - if sub is None: - # if there is no sub menu, bind the handler to the rootItem - rootMenu.Bind(wx.EVT_MENU, cls.handler, rootItem) - elif sub: - # If sub exists and is not False, set submenu. - # Sub might return False when we have a mix of - # single menu items and submenus (see: damage profile - # context menu) - # - # If there is a submenu, it is expected that the sub - # logic take care of it's own bindings, including for - # any single root items. No binding is done here - # - # It is important to remember that when binding sub - # menu items, the menu to bind to depends on platform. - # Windows should bind to rootMenu, and all other - # platforms should bind to sub menu. See existing - # implementations for examples. - rootItem.SetSubMenu(sub) - - if bitmap is not None: - if multiple: - bp = bitmap[it] - if bp: - rootItem.SetBitmap(bp) - else: - rootItem.SetBitmap(bitmap) - - rootMenu.AppendItem(rootItem) - - empty = False - - if amount > 0 and i != len(fullContexts) - 1: - rootMenu.AppendSeparator() - - debug_end = len(cls._ids) - if (debug_end - debug_start): - logger.debug("%d new IDs created for this menu" % (debug_end - debug_start)) - - return rootMenu if empty is False else None - - @classmethod - def handler(cls, event): - menu = event.EventObject - stuff = menu.info.get(event.Id) - if stuff is not None: - menuHandler, context, i = stuff - selection = menu.selection - if not hasattr(selection, "__iter__"): - selection = (selection,) - - menuHandler.activate(context, selection, i) - else: - event.Skip() - - def display(self, context, selection): - raise NotImplementedError() - - def activate(self, fullContext, selection, i): - return None - - def getSubMenu(self, context, selection, rootMenu, i, pitem): - return None - - @classmethod - def nextID(cls): - """ - Fetches an ID from the pool of IDs allocated to Context Menu. - If we don't have enough ID's to fulfill request, create new - ID and add it to the pool. - - See GH Issue #589 - """ - cls._idxid += 1 - - if cls._idxid >= len(cls._ids): # We don't ahve an ID for this index, create one - cls._ids.append(wx.NewId()) - - return cls._ids[cls._idxid] - - def getText(self, context, selection): - """ - getText should be implemented in child classes, and should return either - a string that will make up a menu item label or a list of strings which - will make numerous menu items. - - These menu items will be added to the root menu - """ - raise NotImplementedError() - - def getBitmap(self, context, selection): - return None - - -from gui.builtinContextMenus import * # noqa +# ============================================================================= +# 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 wx +import logging + +logger = logging.getLogger(__name__) + + +class ContextMenu(object): + menus = [] + _ids = [] # [wx.NewId() for x in xrange(200)] # init with decent amount + _idxid = -1 + + @classmethod + def register(cls): + ContextMenu.menus.append(cls) + + @classmethod + def getMenu(cls, selection, *fullContexts): + """ + getMenu returns a menu that is used with wx.PopupMenu. + + selection: provides a list of what was selected. If only 1 item was + selected, it's is a 1-item list or tuple. Can also be None for + contexts without selection, such as statsPane or projected view + fullContexts: a number of tuples of the following tuple: + srcContext - context were menu was spawned, eg: projectedFit, + cargoItem, etc + itemContext - usually the name of the item's category + + eg: + (('fittingModule', 'Module'), ('fittingShip', 'Ship')) + (('marketItemGroup', 'Implant'),) + (('fittingShip', 'Ship'),) + """ + cls._idxid = -1 + debug_start = len(cls._ids) + + rootMenu = wx.Menu() + rootMenu.info = {} + rootMenu.selection = (selection,) if not hasattr(selection, "__iter__") else selection + empty = True + for i, fullContext in enumerate(fullContexts): + amount = 0 + srcContext = fullContext[0] + try: + itemContext = fullContext[1] + except IndexError: + itemContext = None + for menuHandler in cls.menus: + # loop through registered menus + m = menuHandler() + if m.display(srcContext, selection): + amount += 1 + texts = m.getText(itemContext, selection) + if isinstance(texts, basestring): + texts = (texts,) + + bitmap = m.getBitmap(srcContext, selection) + multiple = not isinstance(bitmap, wx.Bitmap) + for it, text in enumerate(texts): + id = cls.nextID() + rootItem = wx.MenuItem(rootMenu, id, text) + rootMenu.info[id] = (m, fullContext, it) + + sub = m.getSubMenu(srcContext, selection, rootMenu, it, rootItem) + + if sub is None: + # if there is no sub menu, bind the handler to the rootItem + rootMenu.Bind(wx.EVT_MENU, cls.handler, rootItem) + elif sub: + # If sub exists and is not False, set submenu. + # Sub might return False when we have a mix of + # single menu items and submenus (see: damage profile + # context menu) + # + # If there is a submenu, it is expected that the sub + # logic take care of it's own bindings, including for + # any single root items. No binding is done here + # + # It is important to remember that when binding sub + # menu items, the menu to bind to depends on platform. + # Windows should bind to rootMenu, and all other + # platforms should bind to sub menu. See existing + # implementations for examples. + rootItem.SetSubMenu(sub) + + if bitmap is not None: + if multiple: + bp = bitmap[it] + if bp: + rootItem.SetBitmap(bp) + else: + rootItem.SetBitmap(bitmap) + + rootMenu.AppendItem(rootItem) + + empty = False + + if amount > 0 and i != len(fullContexts) - 1: + rootMenu.AppendSeparator() + + debug_end = len(cls._ids) + if (debug_end - debug_start): + logger.debug("%d new IDs created for this menu" % (debug_end - debug_start)) + + return rootMenu if empty is False else None + + @classmethod + def handler(cls, event): + menu = event.EventObject + stuff = menu.info.get(event.Id) + if stuff is not None: + menuHandler, context, i = stuff + selection = menu.selection + if not hasattr(selection, "__iter__"): + selection = (selection,) + + menuHandler.activate(context, selection, i) + else: + event.Skip() + + def display(self, context, selection): + raise NotImplementedError() + + def activate(self, fullContext, selection, i): + return None + + def getSubMenu(self, context, selection, rootMenu, i, pitem): + return None + + @classmethod + def nextID(cls): + """ + Fetches an ID from the pool of IDs allocated to Context Menu. + If we don't have enough ID's to fulfill request, create new + ID and add it to the pool. + + See GH Issue #589 + """ + cls._idxid += 1 + + if cls._idxid >= len(cls._ids): # We don't ahve an ID for this index, create one + cls._ids.append(wx.NewId()) + + return cls._ids[cls._idxid] + + def getText(self, context, selection): + """ + getText should be implemented in child classes, and should return either + a string that will make up a menu item label or a list of strings which + will make numerous menu items. + + These menu items will be added to the root menu + """ + raise NotImplementedError() + + def getBitmap(self, context, selection): + return None + + +from gui.builtinContextMenus import * # noqa diff --git a/gui/copySelectDialog.py b/gui/copySelectDialog.py index 659e01b5a..bb5a6e601 100644 --- a/gui/copySelectDialog.py +++ b/gui/copySelectDialog.py @@ -30,7 +30,8 @@ class CopySelectDialog(wx.Dialog): copyFormatMultiBuy = 5 def __init__(self, parent): - wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title=u"Select a format", size=(-1, -1), style=wx.DEFAULT_DIALOG_STYLE) + wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title=u"Select a format", size=(-1, -1), + style=wx.DEFAULT_DIALOG_STYLE) mainSizer = wx.BoxSizer(wx.VERTICAL) copyFormats = [u"EFT", u"EFT (Implants)", u"XML", u"DNA", u"CREST", u"MultiBuy"] @@ -40,7 +41,8 @@ class CopySelectDialog(wx.Dialog): CopySelectDialog.copyFormatDna: u"A one-line text format", CopySelectDialog.copyFormatCrest: u"A JSON format used for EVE CREST", CopySelectDialog.copyFormatMultiBuy: u"MultiBuy text format"} - selector = wx.RadioBox(self, wx.ID_ANY, label=u"Copy to the clipboard using:", choices=copyFormats, style=wx.RA_SPECIFY_ROWS) + selector = wx.RadioBox(self, wx.ID_ANY, label=u"Copy to the clipboard using:", choices=copyFormats, + style=wx.RA_SPECIFY_ROWS) selector.Bind(wx.EVT_RADIOBOX, self.Selected) for format, tooltip in copyFormatTooltips.iteritems(): selector.SetItemToolTip(format, tooltip) diff --git a/gui/crestFittings.py b/gui/crestFittings.py index 585e0f7ec..aa1fdc595 100644 --- a/gui/crestFittings.py +++ b/gui/crestFittings.py @@ -16,9 +16,9 @@ import gui.globalEvents as GE class CrestFittings(wx.Frame): - def __init__(self, parent): - wx.Frame.__init__(self, parent, id=wx.ID_ANY, title="Browse EVE Fittings", pos=wx.DefaultPosition, size=wx.Size( 550,450 ), style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL) + wx.Frame.__init__(self, parent, id=wx.ID_ANY, title="Browse EVE Fittings", pos=wx.DefaultPosition, + size=wx.Size(550, 450), style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL) self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)) @@ -26,42 +26,43 @@ class CrestFittings(wx.Frame): mainSizer = wx.BoxSizer(wx.VERTICAL) sCrest = Crest.getInstance() - characterSelectSizer = wx.BoxSizer( wx.HORIZONTAL ) + characterSelectSizer = wx.BoxSizer(wx.HORIZONTAL) if sCrest.settings.get('mode') == CrestModes.IMPLICIT: - self.stLogged = wx.StaticText(self, wx.ID_ANY, "Currently logged in as %s"%sCrest.implicitCharacter.name, wx.DefaultPosition, wx.DefaultSize) - self.stLogged.Wrap( -1 ) + self.stLogged = wx.StaticText(self, wx.ID_ANY, "Currently logged in as %s" % sCrest.implicitCharacter.name, + wx.DefaultPosition, wx.DefaultSize) + self.stLogged.Wrap(-1) - characterSelectSizer.Add( self.stLogged, 1, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + characterSelectSizer.Add(self.stLogged, 1, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5) else: self.charChoice = wx.Choice(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, []) - characterSelectSizer.Add( self.charChoice, 1, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + characterSelectSizer.Add(self.charChoice, 1, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5) self.updateCharList() - self.fetchBtn = wx.Button( self, wx.ID_ANY, u"Fetch Fits", wx.DefaultPosition, wx.DefaultSize, 5 ) - characterSelectSizer.Add( self.fetchBtn, 0, wx.ALL, 5 ) - mainSizer.Add( characterSelectSizer, 0, wx.EXPAND, 5 ) + self.fetchBtn = wx.Button(self, wx.ID_ANY, u"Fetch Fits", wx.DefaultPosition, wx.DefaultSize, 5) + characterSelectSizer.Add(self.fetchBtn, 0, wx.ALL, 5) + mainSizer.Add(characterSelectSizer, 0, wx.EXPAND, 5) - self.sl = wx.StaticLine( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL ) - mainSizer.Add( self.sl, 0, wx.EXPAND |wx.ALL, 5 ) + self.sl = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) + mainSizer.Add(self.sl, 0, wx.EXPAND | wx.ALL, 5) - contentSizer = wx.BoxSizer( wx.HORIZONTAL ) - browserSizer = wx.BoxSizer( wx.VERTICAL ) + contentSizer = wx.BoxSizer(wx.HORIZONTAL) + browserSizer = wx.BoxSizer(wx.VERTICAL) self.fitTree = FittingsTreeView(self) - browserSizer.Add( self.fitTree, 1, wx.ALL|wx.EXPAND, 5 ) - contentSizer.Add( browserSizer, 1, wx.EXPAND, 0 ) - fitSizer = wx.BoxSizer( wx.VERTICAL ) + browserSizer.Add(self.fitTree, 1, wx.ALL | wx.EXPAND, 5) + contentSizer.Add(browserSizer, 1, wx.EXPAND, 0) + fitSizer = wx.BoxSizer(wx.VERTICAL) self.fitView = FitView(self) - fitSizer.Add( self.fitView, 1, wx.ALL|wx.EXPAND, 5 ) + fitSizer.Add(self.fitView, 1, wx.ALL | wx.EXPAND, 5) - btnSizer = wx.BoxSizer( wx.HORIZONTAL ) - self.importBtn = wx.Button( self, wx.ID_ANY, u"Import to pyfa", wx.DefaultPosition, wx.DefaultSize, 5 ) - self.deleteBtn = wx.Button( self, wx.ID_ANY, u"Delete from EVE", wx.DefaultPosition, wx.DefaultSize, 5 ) - btnSizer.Add( self.importBtn, 1, wx.ALL, 5 ) - btnSizer.Add( self.deleteBtn, 1, wx.ALL, 5 ) - fitSizer.Add( btnSizer, 0, wx.EXPAND ) + btnSizer = wx.BoxSizer(wx.HORIZONTAL) + self.importBtn = wx.Button(self, wx.ID_ANY, u"Import to pyfa", wx.DefaultPosition, wx.DefaultSize, 5) + self.deleteBtn = wx.Button(self, wx.ID_ANY, u"Delete from EVE", wx.DefaultPosition, wx.DefaultSize, 5) + btnSizer.Add(self.importBtn, 1, wx.ALL, 5) + btnSizer.Add(self.deleteBtn, 1, wx.ALL, 5) + fitSizer.Add(btnSizer, 0, wx.EXPAND) contentSizer.Add(fitSizer, 1, wx.EXPAND, 0) mainSizer.Add(contentSizer, 1, wx.EXPAND, 5) @@ -104,12 +105,12 @@ class CrestFittings(wx.Frame): self.charChoice.SetSelection(0) def updateCacheStatus(self, event): - t = time.gmtime(self.cacheTime-time.time()) + t = time.gmtime(self.cacheTime - time.time()) if t < 0: self.cacheTimer.Stop() else: sTime = time.strftime("%H:%M:%S", t) - self.statusbar.SetStatusText("Cached for %s"%sTime, 0) + self.statusbar.SetStatusText("Cached for %s" % sTime, 0) def ssoLogout(self, event): if event.type == CrestModes.IMPLICIT: @@ -163,8 +164,8 @@ class CrestFittings(wx.Frame): data = json.loads(self.fitTree.fittingsTreeCtrl.GetPyData(selection)) dlg = wx.MessageDialog(self, - "Do you really want to delete %s (%s) from EVE?"%(data['name'], data['ship']['name']), - "Confirm Delete", wx.YES | wx.NO | wx.ICON_QUESTION) + "Do you really want to delete %s (%s) from EVE?" % (data['name'], data['ship']['name']), + "Confirm Delete", wx.YES | wx.NO | wx.ICON_QUESTION) if dlg.ShowModal() == wx.ID_YES: try: @@ -174,9 +175,9 @@ class CrestFittings(wx.Frame): class ExportToEve(wx.Frame): - def __init__(self, parent): - wx.Frame.__init__(self, parent, id=wx.ID_ANY, title="Export fit to EVE", pos=wx.DefaultPosition, size=(wx.Size(350,100)), style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL) + wx.Frame.__init__(self, parent, id=wx.ID_ANY, title="Export fit to EVE", pos=wx.DefaultPosition, + size=(wx.Size(350, 100)), style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL) self.mainFrame = parent self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)) @@ -186,20 +187,21 @@ class ExportToEve(wx.Frame): hSizer = wx.BoxSizer(wx.HORIZONTAL) if sCrest.settings.get('mode') == CrestModes.IMPLICIT: - self.stLogged = wx.StaticText(self, wx.ID_ANY, "Currently logged in as %s"%sCrest.implicitCharacter.name, wx.DefaultPosition, wx.DefaultSize) - self.stLogged.Wrap( -1 ) + self.stLogged = wx.StaticText(self, wx.ID_ANY, "Currently logged in as %s" % sCrest.implicitCharacter.name, + wx.DefaultPosition, wx.DefaultSize) + self.stLogged.Wrap(-1) - hSizer.Add( self.stLogged, 1, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + hSizer.Add(self.stLogged, 1, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5) else: self.charChoice = wx.Choice(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, []) - hSizer.Add( self.charChoice, 1, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + hSizer.Add(self.charChoice, 1, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5) self.updateCharList() self.charChoice.SetSelection(0) - self.exportBtn = wx.Button( self, wx.ID_ANY, u"Export Fit", wx.DefaultPosition, wx.DefaultSize, 5 ) - hSizer.Add( self.exportBtn, 0, wx.ALL, 5 ) + self.exportBtn = wx.Button(self, wx.ID_ANY, u"Export Fit", wx.DefaultPosition, wx.DefaultSize, 5) + hSizer.Add(self.exportBtn, 0, wx.ALL, 5) - mainSizer.Add( hSizer, 0, wx.EXPAND, 5 ) + mainSizer.Add(hSizer, 0, wx.EXPAND, 5) self.exportBtn.Bind(wx.EVT_BUTTON, self.exportFitting) @@ -271,7 +273,7 @@ class ExportToEve(wx.Frame): data = sFit.exportCrest(fitID) res = sCrest.postFitting(self.getActiveCharacter(), data) - self.statusbar.SetStatusText("%d: %s"%(res.status_code, res.reason), 0) + self.statusbar.SetStatusText("%d: %s" % (res.status_code, res.reason), 0) try: text = json.loads(res.text) self.statusbar.SetStatusText(text['message'], 1) @@ -282,40 +284,40 @@ class ExportToEve(wx.Frame): class CrestMgmt(wx.Dialog): - - def __init__( self, parent ): - wx.Dialog.__init__ ( self, parent, id = wx.ID_ANY, title = "CREST Character Management", pos = wx.DefaultPosition, size = wx.Size( 550,250 ), style = wx.DEFAULT_DIALOG_STYLE ) + def __init__(self, parent): + wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title="CREST Character Management", pos=wx.DefaultPosition, + size=wx.Size(550, 250), style=wx.DEFAULT_DIALOG_STYLE) self.mainFrame = parent - mainSizer = wx.BoxSizer( wx.HORIZONTAL ) + mainSizer = wx.BoxSizer(wx.HORIZONTAL) - self.lcCharacters = wx.ListCtrl( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LC_REPORT) + self.lcCharacters = wx.ListCtrl(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LC_REPORT) self.lcCharacters.InsertColumn(0, heading='Character') self.lcCharacters.InsertColumn(1, heading='Refresh Token') self.popCharList() - mainSizer.Add( self.lcCharacters, 1, wx.ALL|wx.EXPAND, 5 ) + mainSizer.Add(self.lcCharacters, 1, wx.ALL | wx.EXPAND, 5) - btnSizer = wx.BoxSizer( wx.VERTICAL ) + btnSizer = wx.BoxSizer(wx.VERTICAL) - self.addBtn = wx.Button( self, wx.ID_ANY, u"Add Character", wx.DefaultPosition, wx.DefaultSize, 0 ) - btnSizer.Add( self.addBtn, 0, wx.ALL | wx.EXPAND, 5 ) + self.addBtn = wx.Button(self, wx.ID_ANY, u"Add Character", wx.DefaultPosition, wx.DefaultSize, 0) + btnSizer.Add(self.addBtn, 0, wx.ALL | wx.EXPAND, 5) - self.deleteBtn = wx.Button( self, wx.ID_ANY, u"Revoke Character", wx.DefaultPosition, wx.DefaultSize, 0 ) - btnSizer.Add( self.deleteBtn, 0, wx.ALL | wx.EXPAND, 5 ) + self.deleteBtn = wx.Button(self, wx.ID_ANY, u"Revoke Character", wx.DefaultPosition, wx.DefaultSize, 0) + btnSizer.Add(self.deleteBtn, 0, wx.ALL | wx.EXPAND, 5) - mainSizer.Add( btnSizer, 0, wx.EXPAND, 5 ) + mainSizer.Add(btnSizer, 0, wx.EXPAND, 5) self.addBtn.Bind(wx.EVT_BUTTON, self.addChar) self.deleteBtn.Bind(wx.EVT_BUTTON, self.delChar) self.mainFrame.Bind(GE.EVT_SSO_LOGIN, self.ssoLogin) - self.SetSizer( mainSizer ) + self.SetSizer(mainSizer) self.Layout() - self.Centre( wx.BOTH ) + self.Centre(wx.BOTH) def ssoLogin(self, event): self.popCharList() diff --git a/gui/display.py b/gui/display.py index 2aebc1db7..0d9e0308f 100644 --- a/gui/display.py +++ b/gui/display.py @@ -70,9 +70,8 @@ class Display(wx.ListCtrl): self.imageListBase = self.imageList.ImageCount - -# Override native HitTestSubItem (doesn't work as it should on GTK) -# Source: ObjectListView + # Override native HitTestSubItem (doesn't work as it should on GTK) + # Source: ObjectListView def HitTestSubItem(self, pt): """ @@ -282,7 +281,7 @@ class Display(wx.ListCtrl): self.SetColumnWidth(i, headerWidth) else: self.SetColumnWidth(i, col.size) - # self.Thaw() + # self.Thaw() def update(self, stuff): self.populate(stuff) diff --git a/gui/fighterView.py b/gui/fighterView.py index 56aeb73b1..524c07976 100644 --- a/gui/fighterView.py +++ b/gui/fighterView.py @@ -28,24 +28,25 @@ from eos.types import Slot from gui.contextMenu import ContextMenu from service.fit import Fit -class FighterViewDrop(wx.PyDropTarget): - def __init__(self, dropFn): - wx.PyDropTarget.__init__(self) - self.dropFn = dropFn - # this is really transferring an EVE itemID - self.dropData = wx.PyTextDataObject() - self.SetDataObject(self.dropData) - def OnData(self, x, y, t): - if self.GetData(): - data = self.dropData.GetText().split(':') - self.dropFn(x, y, data) - return t +class FighterViewDrop(wx.PyDropTarget): + def __init__(self, dropFn): + wx.PyDropTarget.__init__(self) + self.dropFn = dropFn + # this is really transferring an EVE itemID + self.dropData = wx.PyTextDataObject() + self.SetDataObject(self.dropData) + + def OnData(self, x, y, t): + if self.GetData(): + data = self.dropData.GetText().split(':') + self.dropFn(x, y, data) + return t class FighterView(wx.Panel): def __init__(self, parent): - wx.Panel.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, style=wx.TAB_TRAVERSAL ) + wx.Panel.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, style=wx.TAB_TRAVERSAL) self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.labels = ["Light", "Heavy", "Support"] @@ -55,7 +56,7 @@ class FighterView(wx.Panel): mainSizer.Add(self.fighterDisplay, 1, wx.EXPAND, 0) textSizer = wx.BoxSizer(wx.HORIZONTAL) - textSizer.AddSpacer(( 0, 0), 1, wx.EXPAND, 5) + textSizer.AddSpacer((0, 0), 1, wx.EXPAND, 5) for x in self.labels: lbl = wx.StaticText(self, wx.ID_ANY, x.capitalize()) @@ -74,10 +75,9 @@ class FighterView(wx.Panel): mainSizer.Add(textSizer, 0, wx.EXPAND, 5) - self.SetSizer( mainSizer ) + self.SetSizer(mainSizer) self.SetAutoLayout(True) - self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged) def fitChanged(self, event): @@ -90,7 +90,8 @@ class FighterView(wx.Panel): slot = getattr(Slot, "F_{}".format(x.upper())) used = fit.getSlotsUsed(slot) total = fit.getNumSlots(slot) - color = wx.Colour(204, 51, 51) if used > total else wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOWTEXT) + color = wx.Colour(204, 51, 51) if used > total else wx.SystemSettings_GetColour( + wx.SYS_COLOUR_WINDOWTEXT) lbl = getattr(self, "label%sUsed" % x.capitalize()) lbl.SetLabel(str(int(used))) @@ -105,15 +106,15 @@ class FighterView(wx.Panel): class FighterDisplay(d.Display): DEFAULT_COLS = ["State", - #"Base Icon", + # "Base Icon", "Base Name", # "prop:droneDps,droneBandwidth", - #"Max Range", - #"Miscellanea", + # "Max Range", + # "Miscellanea", "attr:maxVelocity", "Fighter Abilities" - #"Price", - ] + # "Price", + ] def __init__(self, parent): d.Display.__init__(self, parent, style=wx.LC_SINGLE_SEL | wx.BORDER_NONE) @@ -131,12 +132,11 @@ class FighterDisplay(d.Display): self.Bind(wx.EVT_MOTION, self.OnMouseMove) self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow) - if "__WXGTK__" in wx.PlatformInfo: + if "__WXGTK__" in wx.PlatformInfo: self.Bind(wx.EVT_RIGHT_UP, self.scheduleMenu) else: self.Bind(wx.EVT_RIGHT_DOWN, self.scheduleMenu) - self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.startDrag) self.SetDropTarget(FighterViewDrop(self.handleDragDrop)) @@ -183,7 +183,7 @@ class FighterDisplay(d.Display): row = event.GetIndex() if row != -1: data = wx.PyTextDataObject() - data.SetText("fighter:"+str(row)) + data.SetText("fighter:" + str(row)) dropSource = wx.DropSource(self) dropSource.SetData(data) @@ -229,7 +229,7 @@ class FighterDisplay(d.Display): self.Parent.Parent.Parent.DisablePage(self.Parent, not fit) - #Clear list and get out if current fitId is None + # Clear list and get out if current fitId is None if event.fitID is None and self.lastFitId is not None: self.DeleteAllItems() self.lastFitId = None @@ -257,7 +257,6 @@ class FighterDisplay(d.Display): self.update(stuff) event.Skip() - def addItem(self, event): sFit = Fit.getInstance() fitID = self.mainFrame.getActiveFit() diff --git a/gui/fleetBrowser.py b/gui/fleetBrowser.py index 1d3c6b38c..ab50e58ca 100644 --- a/gui/fleetBrowser.py +++ b/gui/fleetBrowser.py @@ -1,448 +1,449 @@ -import wx -from wx.lib.buttons import GenBitmapButton - -import service.fleet -import gui.mainFrame -import gui.utils.colorUtils as colorUtils -import gui.sfBrowserItem as SFItem -from gui.bitmapLoader import BitmapLoader -from gui.PFListPane import PFListPane -from gui.utils.drawUtils import GetPartialText - - -FleetSelected, EVT_FLEET_SELECTED = wx.lib.newevent.NewEvent() -FleetRenamed, EVT_FLEET_RENAMED = wx.lib.newevent.NewEvent() -FleetRemoved, EVT_FLEET_REMOVED = wx.lib.newevent.NewEvent() -FleetItemSelect, EVT_FLEET_ITEM_SELECT = wx.lib.newevent.NewEvent() -FleetItemDelete, EVT_FLEET_ITEM_DELETE = wx.lib.newevent.NewEvent() -FleetItemNew, EVT_FLEET_ITEM_NEW = wx.lib.newevent.NewEvent() -FleetItemCopy, EVT_FLEET_ITEM_COPY = wx.lib.newevent.NewEvent() -FleetItemRename, EVT_FLEET_ITEM_RENAME = wx.lib.newevent.NewEvent() - - -class FleetBrowser(wx.Panel): - def __init__(self, parent): - wx.Panel.__init__(self, parent) - - self.sFleet = service.fleet.Fleet.getInstance() - self.mainFrame = gui.mainFrame.MainFrame.getInstance() - - mainSizer = wx.BoxSizer(wx.VERTICAL) - - self.hpane = FleetBrowserHeader(self) - mainSizer.Add(self.hpane, 0, wx.EXPAND) - - self.m_sl2 = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) - mainSizer.Add(self.m_sl2, 0, wx.EXPAND, 0) - - self.fleetItemContainer = PFFleetItemContainer(self) - - mainSizer.Add(self.fleetItemContainer, 1, wx.EXPAND) - - self.SetSizer(mainSizer) - self.Layout() - - self.filter = "" - self.fleetIDMustEditName = -1 - - self.Bind(wx.EVT_SIZE, self.SizeRefreshList) - - self.Bind(EVT_FLEET_ITEM_NEW, self.AddNewFleetItem) - self.Bind(EVT_FLEET_ITEM_SELECT, self.SelectFleetItem) - self.Bind(EVT_FLEET_ITEM_DELETE, self.DeleteFleetItem) - self.Bind(EVT_FLEET_ITEM_COPY, self.CopyFleetItem) - self.Bind(EVT_FLEET_ITEM_RENAME, self.RenameFleetItem) - - self.PopulateFleetList() - - def AddNewFleetItem(self, event): - fleetName = event.fleetName - newFleet = self.sFleet.addFleet() - self.sFleet.renameFleet(newFleet, fleetName) - - self.fleetIDMustEditName = newFleet.ID - self.AddItem(newFleet.ID, newFleet.name, newFleet.count()) - - def SelectFleetItem(self, event): - fleetID = event.fleetID - self.fleetItemContainer.SelectWidgetByFleetID(fleetID) - wx.PostEvent(self.mainFrame, FleetSelected(fleetID=fleetID)) - - def CopyFleetItem(self, event): - fleetID = event.fleetID - fleet = self.sFleet.copyFleetByID(fleetID) - - fleetName = fleet.name + " Copy" - self.sFleet.renameFleet(fleet, fleetName) - - self.fleetIDMustEditName = fleet.ID - self.AddItem(fleet.ID, fleet.name, fleet.count()) - - self.fleetItemContainer.SelectWidgetByFleetID(fleet.ID) - wx.PostEvent(self.mainFrame, FleetSelected(fleetID=fleet.ID)) - - def RenameFleetItem(self, event): - fleetID = event.fleetID - fleet = self.sFleet.getFleetByID(fleetID) - - newFleetName = event.fleetName - - self.sFleet.renameFleet(fleet, newFleetName) - wx.PostEvent(self.mainFrame, FleetRenamed(fleetID=fleet.ID)) - - def DeleteFleetItem(self, event): - self.sFleet.deleteFleetByID(event.fleetID) - self.PopulateFleetList() - wx.PostEvent(self.mainFrame, FleetRemoved(fleetID=event.fleetID)) - - def AddItem(self, ID, name, count): - self.fleetItemContainer.AddWidget(FleetItem(self, ID, name, count)) - widget = self.fleetItemContainer.GetWidgetByFleetID(ID) - self.fleetItemContainer.RefreshList(True) - self.fleetItemContainer.ScrollChildIntoView(widget) - wx.PostEvent(self, FleetItemSelect(fleetID=ID)) - - def PopulateFleetList(self): - self.Freeze() - filter_ = self.filter - self.fleetItemContainer.RemoveAllChildren() - fleetList = self.sFleet.getFleetList() - for fleetID, fleetName, fleetCount in fleetList: - if fleetName.lower().find(filter_.lower()) != -1: - self.fleetItemContainer.AddWidget(FleetItem(self, fleetID, fleetName, fleetCount)) - self.fleetItemContainer.RefreshList() - self.Thaw() - - def SetFilter(self, filter): - self.filter = filter - - def SizeRefreshList(self, event): - ewidth, eheight = event.GetSize() - self.Layout() - self.fleetItemContainer.Layout() - self.fleetItemContainer.RefreshList(True) - event.Skip() - - -class FleetBrowserHeader(wx.Panel): - def __init__(self, parent): - wx.Panel.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.Size(500, 24), style=wx.TAB_TRAVERSAL) - self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)) - - self.newBmp = BitmapLoader.getBitmap("fit_add_small", "gui") - bmpSize = (16, 16) - - mainSizer = wx.BoxSizer(wx.HORIZONTAL) - - if 'wxMac' in wx.PlatformInfo: - bgcolour = wx.Colour(0, 0, 0, 0) - else: - bgcolour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE) - - self.fbNewFleet = PFGenBitmapButton(self, wx.ID_ANY, self.newBmp, wx.DefaultPosition, bmpSize, wx.BORDER_NONE) - mainSizer.Add(self.fbNewFleet, 0, wx.LEFT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL, 5) - self.fbNewFleet.SetBackgroundColour(bgcolour) - - self.sl1 = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_VERTICAL) - mainSizer.Add(self.sl1, 0, wx.EXPAND | wx.LEFT, 5) - - self.tcFilter = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0) - mainSizer.Add(self.tcFilter, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5) - - self.stStatus = wx.StaticText(self, wx.ID_ANY, u"", wx.DefaultPosition, wx.DefaultSize, 0) - self.stStatus.Wrap(-1) - mainSizer.Add(self.stStatus, 1, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5) - - self.SetSizer(mainSizer) - self.Layout() - - self.fbNewFleet.Bind(wx.EVT_ENTER_WINDOW, self.fbNewEnterWindow) - self.fbNewFleet.Bind(wx.EVT_LEAVE_WINDOW, self.fbHItemLeaveWindow) - self.fbNewFleet.Bind(wx.EVT_BUTTON, self.OnNewFleetItem) - - self.tcFilter.Bind(wx.EVT_TEXT, self.OnFilterText) - - self.tcFilter.Bind(wx.EVT_ENTER_WINDOW, self.fbFilterEnterWindow) - self.tcFilter.Bind(wx.EVT_LEAVE_WINDOW, self.fbHItemLeaveWindow) - - def OnFilterText(self, event): - filter = self.tcFilter.GetValue() - self.Parent.SetFilter(filter) - self.Parent.PopulateFleetList() - event.Skip() - - def OnNewFleetItem(self, event): - wx.PostEvent(self.Parent, FleetItemNew(fleetName="New Fleet")) - - def fbNewEnterWindow(self, event): - self.stStatus.SetLabel("New fleet") - self.Parent.SetCursor(wx.StockCursor(wx.CURSOR_HAND)) - event.Skip() - - def fbHItemLeaveWindow(self, event): - self.stStatus.SetLabel("") - self.Parent.SetCursor(wx.StockCursor(wx.CURSOR_ARROW)) - event.Skip() - - def fbFilterEnterWindow(self, event): - self.stStatus.SetLabel("Filter list") - event.Skip() - - -class PFFleetItemContainer(PFListPane): - def __init__(self, parent): - PFListPane.__init__(self, parent) - self.selectedWidget = -1 - self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) - - def IsWidgetSelectedByContext(self, widget): - if self.GetWidgetList()[widget].IsSelected(): - return True - return False - - def GetWidgetIndex(self, widgetWnd): - return self.GetWidgetList().index(widgetWnd) - - def GetWidgetByFleetID(self, fleetID): - for widget in self.GetWidgetList(): - if widget.fleetID == fleetID: - return widget - return None - - def SelectWidget(self, widgetWnd): - wlist = self.GetWidgetList() - if self.selectedWidget != -1: - wlist[self.selectedWidget].SetSelected(False) - wlist[self.selectedWidget].Refresh() - windex = self.GetWidgetIndex(widgetWnd) - wlist[windex].SetSelected(True) - wlist[windex].Refresh() - self.selectedWidget = windex - - def SelectWidgetByFleetID(self, fleetID): - widgetWnd = self.GetWidgetByFleetID(fleetID) - if widgetWnd: - self.SelectWidget(widgetWnd) - - def RemoveWidget(self, child): - child.Destroy() - self.selectedWidget = -1 - self._wList.remove(child) - - def RemoveAllChildren(self): - for widget in self._wList: - widget.Destroy() - - self.selectedWidget = -1 - self._wList = [] - - def OnLeftUp(self, event): - event.Skip() - - -class FleetItem(SFItem.SFBrowserItem): - def __init__(self, parent, fleetID, fleetName, fleetCount, - id=wx.ID_ANY, pos=wx.DefaultPosition, - size=(0, 40), style=0): - SFItem.SFBrowserItem.__init__(self, parent, size=size) - - self.fleetBrowser = self.Parent - self.fleetID = fleetID - self.fleetName = fleetName - self.fleetCount = fleetCount - - self.padding = 4 - - self.fontBig = wx.FontFromPixelSize((0, 15), wx.SWISS, wx.NORMAL, wx.BOLD, False) - self.fontNormal = wx.FontFromPixelSize((0, 14), wx.SWISS, wx.NORMAL, wx.NORMAL, False) - self.fontSmall = wx.FontFromPixelSize((0, 12), wx.SWISS, wx.NORMAL, wx.NORMAL, False) - - self.copyBmp = BitmapLoader.getBitmap("fit_add_small", "gui") - self.renameBmp = BitmapLoader.getBitmap("fit_rename_small", "gui") - self.deleteBmp = BitmapLoader.getBitmap("fit_delete_small", "gui") - self.acceptBmp = BitmapLoader.getBitmap("faccept_small", "gui") - self.fleetBmp = BitmapLoader.getBitmap("fleet_item_big", "gui") - - fleetImg = self.fleetBmp.ConvertToImage() - fleetImg = fleetImg.Blur(2) - - if not fleetImg.HasAlpha(): - fleetImg.InitAlpha() - - fleetImg = fleetImg.AdjustChannels(1, 1, 1, 0.5) - self.fleetEffBmp = wx.BitmapFromImage(fleetImg) - - self.toolbar.AddButton(self.copyBmp, "Copy", self.CopyFleetCB) - self.renameBtn = self.toolbar.AddButton(self.renameBmp, "Rename", self.RenameFleetCB) - self.toolbar.AddButton(self.deleteBmp, "Delete", self.DeleteFleetCB) - - self.editWidth = 150 - self.tcFleetName = wx.TextCtrl(self, wx.ID_ANY, "%s" % self.fleetName, wx.DefaultPosition, (self.editWidth, -1), wx.TE_PROCESS_ENTER) - - if self.fleetBrowser.fleetIDMustEditName != self.fleetID: - self.tcFleetName.Show(False) - else: - self.tcFleetName.SetFocus() - self.tcFleetName.SelectAll() - self.fleetBrowser.fleetIDMustEditName = -1 - self.renameBtn.SetBitmap(self.acceptBmp) - self.selected = True - - self.tcFleetName.Bind(wx.EVT_KILL_FOCUS, self.OnEditLostFocus) - self.tcFleetName.Bind(wx.EVT_TEXT_ENTER, self.RenameFleet) - self.tcFleetName.Bind(wx.EVT_KEY_DOWN, self.EditCheckEsc) - - self.animCount = 0 - - def MouseLeftUp(self, event): - if self.tcFleetName.IsShown(): - self.RestoreEditButton() - else: - wx.PostEvent(self.fleetBrowser, FleetItemSelect(fleetID=self.fleetID)) - - def CopyFleetCB(self): - if self.tcFleetName.IsShown(): - self.RestoreEditButton() - return - - wx.PostEvent(self.fleetBrowser, FleetItemCopy(fleetID=self.fleetID)) - - def RenameFleetCB(self): - - if self.tcFleetName.IsShown(): - - self.RenameFleet(None) - self.RestoreEditButton() - - else: - self.tcFleetName.SetValue(self.fleetName) - self.tcFleetName.Show() - - self.renameBtn.SetBitmap(self.acceptBmp) - self.Refresh() - - self.tcFleetName.SetFocus() - self.tcFleetName.SelectAll() - - self.Refresh() - - def RenameFleet(self, event): - - newFleetName = self.tcFleetName.GetValue() - self.fleetName = newFleetName - - self.tcFleetName.Show(False) - - wx.PostEvent(self.fleetBrowser, FleetItemRename(fleetID=self.fleetID, fleetName=self.fleetName)) - self.Refresh() - - def DeleteFleetCB(self): - if self.tcFleetName.IsShown(): - self.RestoreEditButton() - return - wx.PostEvent(self.fleetBrowser, FleetItemDelete(fleetID=self.fleetID)) - - def RestoreEditButton(self): - self.tcFleetName.Show(False) - self.renameBtn.SetBitmap(self.renameBmp) - self.Refresh() - - def OnEditLostFocus(self, event): - self.RestoreEditButton() - self.Refresh() - - def EditCheckEsc(self, event): - if event.GetKeyCode() == wx.WXK_ESCAPE: - self.RestoreEditButton() - else: - event.Skip() - - def IsSelected(self): - return self.selected - - def UpdateElementsPos(self, mdc): - rect = self.GetRect() - - self.toolbarx = rect.width - self.toolbar.GetWidth() - self.padding - self.toolbary = (rect.height - self.toolbar.GetHeight()) / 2 - - self.toolbarx = self.toolbarx + self.animCount - - self.fleetBmpx = self.padding + (rect.height - self.fleetBmp.GetWidth()) / 2 - self.fleetBmpy = (rect.height - self.fleetBmp.GetHeight()) / 2 - - self.fleetBmpx -= self.animCount - - self.textStartx = self.fleetBmpx + self.fleetBmp.GetWidth() + self.padding - - self.fleetNamey = (rect.height - self.fleetBmp.GetHeight()) / 2 - - mdc.SetFont(self.fontBig) - wtext, htext = mdc.GetTextExtent(self.fleetName) - - self.fleetCounty = self.fleetNamey + htext - - mdc.SetFont(self.fontSmall) - - wlabel, hlabel = mdc.GetTextExtent(self.toolbar.hoverLabel) - - self.thoverx = self.toolbarx - self.padding - wlabel - self.thovery = (rect.height - hlabel) / 2 - self.thoverw = wlabel - - def DrawItem(self, mdc): - # rect = self.GetRect() - - windowColor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW) - textColor = colorUtils.GetSuitableColor(windowColor, 1) - - mdc.SetTextForeground(textColor) - - self.UpdateElementsPos(mdc) - - self.toolbar.SetPosition((self.toolbarx, self.toolbary)) - mdc.DrawBitmap(self.fleetEffBmp, self.fleetBmpx + 3, self.fleetBmpy + 2) - mdc.DrawBitmap(self.fleetBmp, self.fleetBmpx, self.fleetBmpy) - - mdc.SetFont(self.fontNormal) - - suffix = "%d ships" % self.fleetCount if self.fleetCount > 1 else "%d ship" % self.fleetCount if self.fleetCount == 1 else "No ships" - fleetCount = "Fleet size: %s" % suffix - fleetCount = GetPartialText(mdc, fleetCount, self.toolbarx - self.textStartx - self.padding * 2 - self.thoverw) - - mdc.DrawText(fleetCount, self.textStartx, self.fleetCounty) - - mdc.SetFont(self.fontSmall) - mdc.DrawText(self.toolbar.hoverLabel, self.thoverx, self.thovery) - - mdc.SetFont(self.fontBig) - - pfname = GetPartialText(mdc, self.fleetName, self.toolbarx - self.textStartx - self.padding * 2 - self.thoverw) - mdc.DrawText(pfname, self.textStartx, self.fleetNamey) - - if self.tcFleetName.IsShown(): - self.AdjustControlSizePos(self.tcFleetName, self.textStartx, self.toolbarx - self.editWidth - self.padding) - - def AdjustControlSizePos(self, editCtl, start, end): - fnEditSize = editCtl.GetSize() - wSize = self.GetSize() - fnEditPosX = end - fnEditPosY = (wSize.height - fnEditSize.height) / 2 - if fnEditPosX < start: - editCtl.SetSize((self.editWidth + fnEditPosX - start, -1)) - editCtl.SetPosition((start, fnEditPosY)) - else: - editCtl.SetSize((self.editWidth, -1)) - editCtl.SetPosition((fnEditPosX, fnEditPosY)) - - -class PFGenBitmapButton(GenBitmapButton): - def __init__(self, parent, id, bitmap, pos, size, style): - GenBitmapButton.__init__(self, parent, id, bitmap, pos, size, style) - self.bgcolor = wx.Brush(wx.WHITE) - - def SetBackgroundColour(self, color): - self.bgcolor = wx.Brush(color) - - def GetBackgroundBrush(self, dc): - return self.bgcolor +import wx +from wx.lib.buttons import GenBitmapButton + +import service.fleet +import gui.mainFrame +import gui.utils.colorUtils as colorUtils +import gui.sfBrowserItem as SFItem +from gui.bitmapLoader import BitmapLoader +from gui.PFListPane import PFListPane +from gui.utils.drawUtils import GetPartialText + +FleetSelected, EVT_FLEET_SELECTED = wx.lib.newevent.NewEvent() +FleetRenamed, EVT_FLEET_RENAMED = wx.lib.newevent.NewEvent() +FleetRemoved, EVT_FLEET_REMOVED = wx.lib.newevent.NewEvent() +FleetItemSelect, EVT_FLEET_ITEM_SELECT = wx.lib.newevent.NewEvent() +FleetItemDelete, EVT_FLEET_ITEM_DELETE = wx.lib.newevent.NewEvent() +FleetItemNew, EVT_FLEET_ITEM_NEW = wx.lib.newevent.NewEvent() +FleetItemCopy, EVT_FLEET_ITEM_COPY = wx.lib.newevent.NewEvent() +FleetItemRename, EVT_FLEET_ITEM_RENAME = wx.lib.newevent.NewEvent() + + +class FleetBrowser(wx.Panel): + def __init__(self, parent): + wx.Panel.__init__(self, parent) + + self.sFleet = service.fleet.Fleet.getInstance() + self.mainFrame = gui.mainFrame.MainFrame.getInstance() + + mainSizer = wx.BoxSizer(wx.VERTICAL) + + self.hpane = FleetBrowserHeader(self) + mainSizer.Add(self.hpane, 0, wx.EXPAND) + + self.m_sl2 = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) + mainSizer.Add(self.m_sl2, 0, wx.EXPAND, 0) + + self.fleetItemContainer = PFFleetItemContainer(self) + + mainSizer.Add(self.fleetItemContainer, 1, wx.EXPAND) + + self.SetSizer(mainSizer) + self.Layout() + + self.filter = "" + self.fleetIDMustEditName = -1 + + self.Bind(wx.EVT_SIZE, self.SizeRefreshList) + + self.Bind(EVT_FLEET_ITEM_NEW, self.AddNewFleetItem) + self.Bind(EVT_FLEET_ITEM_SELECT, self.SelectFleetItem) + self.Bind(EVT_FLEET_ITEM_DELETE, self.DeleteFleetItem) + self.Bind(EVT_FLEET_ITEM_COPY, self.CopyFleetItem) + self.Bind(EVT_FLEET_ITEM_RENAME, self.RenameFleetItem) + + self.PopulateFleetList() + + def AddNewFleetItem(self, event): + fleetName = event.fleetName + newFleet = self.sFleet.addFleet() + self.sFleet.renameFleet(newFleet, fleetName) + + self.fleetIDMustEditName = newFleet.ID + self.AddItem(newFleet.ID, newFleet.name, newFleet.count()) + + def SelectFleetItem(self, event): + fleetID = event.fleetID + self.fleetItemContainer.SelectWidgetByFleetID(fleetID) + wx.PostEvent(self.mainFrame, FleetSelected(fleetID=fleetID)) + + def CopyFleetItem(self, event): + fleetID = event.fleetID + fleet = self.sFleet.copyFleetByID(fleetID) + + fleetName = fleet.name + " Copy" + self.sFleet.renameFleet(fleet, fleetName) + + self.fleetIDMustEditName = fleet.ID + self.AddItem(fleet.ID, fleet.name, fleet.count()) + + self.fleetItemContainer.SelectWidgetByFleetID(fleet.ID) + wx.PostEvent(self.mainFrame, FleetSelected(fleetID=fleet.ID)) + + def RenameFleetItem(self, event): + fleetID = event.fleetID + fleet = self.sFleet.getFleetByID(fleetID) + + newFleetName = event.fleetName + + self.sFleet.renameFleet(fleet, newFleetName) + wx.PostEvent(self.mainFrame, FleetRenamed(fleetID=fleet.ID)) + + def DeleteFleetItem(self, event): + self.sFleet.deleteFleetByID(event.fleetID) + self.PopulateFleetList() + wx.PostEvent(self.mainFrame, FleetRemoved(fleetID=event.fleetID)) + + def AddItem(self, ID, name, count): + self.fleetItemContainer.AddWidget(FleetItem(self, ID, name, count)) + widget = self.fleetItemContainer.GetWidgetByFleetID(ID) + self.fleetItemContainer.RefreshList(True) + self.fleetItemContainer.ScrollChildIntoView(widget) + wx.PostEvent(self, FleetItemSelect(fleetID=ID)) + + def PopulateFleetList(self): + self.Freeze() + filter_ = self.filter + self.fleetItemContainer.RemoveAllChildren() + fleetList = self.sFleet.getFleetList() + for fleetID, fleetName, fleetCount in fleetList: + if fleetName.lower().find(filter_.lower()) != -1: + self.fleetItemContainer.AddWidget(FleetItem(self, fleetID, fleetName, fleetCount)) + self.fleetItemContainer.RefreshList() + self.Thaw() + + def SetFilter(self, filter): + self.filter = filter + + def SizeRefreshList(self, event): + ewidth, eheight = event.GetSize() + self.Layout() + self.fleetItemContainer.Layout() + self.fleetItemContainer.RefreshList(True) + event.Skip() + + +class FleetBrowserHeader(wx.Panel): + def __init__(self, parent): + wx.Panel.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.Size(500, 24), + style=wx.TAB_TRAVERSAL) + self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)) + + self.newBmp = BitmapLoader.getBitmap("fit_add_small", "gui") + bmpSize = (16, 16) + + mainSizer = wx.BoxSizer(wx.HORIZONTAL) + + if 'wxMac' in wx.PlatformInfo: + bgcolour = wx.Colour(0, 0, 0, 0) + else: + bgcolour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE) + + self.fbNewFleet = PFGenBitmapButton(self, wx.ID_ANY, self.newBmp, wx.DefaultPosition, bmpSize, wx.BORDER_NONE) + mainSizer.Add(self.fbNewFleet, 0, wx.LEFT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL, 5) + self.fbNewFleet.SetBackgroundColour(bgcolour) + + self.sl1 = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_VERTICAL) + mainSizer.Add(self.sl1, 0, wx.EXPAND | wx.LEFT, 5) + + self.tcFilter = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0) + mainSizer.Add(self.tcFilter, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5) + + self.stStatus = wx.StaticText(self, wx.ID_ANY, u"", wx.DefaultPosition, wx.DefaultSize, 0) + self.stStatus.Wrap(-1) + mainSizer.Add(self.stStatus, 1, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5) + + self.SetSizer(mainSizer) + self.Layout() + + self.fbNewFleet.Bind(wx.EVT_ENTER_WINDOW, self.fbNewEnterWindow) + self.fbNewFleet.Bind(wx.EVT_LEAVE_WINDOW, self.fbHItemLeaveWindow) + self.fbNewFleet.Bind(wx.EVT_BUTTON, self.OnNewFleetItem) + + self.tcFilter.Bind(wx.EVT_TEXT, self.OnFilterText) + + self.tcFilter.Bind(wx.EVT_ENTER_WINDOW, self.fbFilterEnterWindow) + self.tcFilter.Bind(wx.EVT_LEAVE_WINDOW, self.fbHItemLeaveWindow) + + def OnFilterText(self, event): + filter = self.tcFilter.GetValue() + self.Parent.SetFilter(filter) + self.Parent.PopulateFleetList() + event.Skip() + + def OnNewFleetItem(self, event): + wx.PostEvent(self.Parent, FleetItemNew(fleetName="New Fleet")) + + def fbNewEnterWindow(self, event): + self.stStatus.SetLabel("New fleet") + self.Parent.SetCursor(wx.StockCursor(wx.CURSOR_HAND)) + event.Skip() + + def fbHItemLeaveWindow(self, event): + self.stStatus.SetLabel("") + self.Parent.SetCursor(wx.StockCursor(wx.CURSOR_ARROW)) + event.Skip() + + def fbFilterEnterWindow(self, event): + self.stStatus.SetLabel("Filter list") + event.Skip() + + +class PFFleetItemContainer(PFListPane): + def __init__(self, parent): + PFListPane.__init__(self, parent) + self.selectedWidget = -1 + self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) + + def IsWidgetSelectedByContext(self, widget): + if self.GetWidgetList()[widget].IsSelected(): + return True + return False + + def GetWidgetIndex(self, widgetWnd): + return self.GetWidgetList().index(widgetWnd) + + def GetWidgetByFleetID(self, fleetID): + for widget in self.GetWidgetList(): + if widget.fleetID == fleetID: + return widget + return None + + def SelectWidget(self, widgetWnd): + wlist = self.GetWidgetList() + if self.selectedWidget != -1: + wlist[self.selectedWidget].SetSelected(False) + wlist[self.selectedWidget].Refresh() + windex = self.GetWidgetIndex(widgetWnd) + wlist[windex].SetSelected(True) + wlist[windex].Refresh() + self.selectedWidget = windex + + def SelectWidgetByFleetID(self, fleetID): + widgetWnd = self.GetWidgetByFleetID(fleetID) + if widgetWnd: + self.SelectWidget(widgetWnd) + + def RemoveWidget(self, child): + child.Destroy() + self.selectedWidget = -1 + self._wList.remove(child) + + def RemoveAllChildren(self): + for widget in self._wList: + widget.Destroy() + + self.selectedWidget = -1 + self._wList = [] + + def OnLeftUp(self, event): + event.Skip() + + +class FleetItem(SFItem.SFBrowserItem): + def __init__(self, parent, fleetID, fleetName, fleetCount, + id=wx.ID_ANY, pos=wx.DefaultPosition, + size=(0, 40), style=0): + SFItem.SFBrowserItem.__init__(self, parent, size=size) + + self.fleetBrowser = self.Parent + self.fleetID = fleetID + self.fleetName = fleetName + self.fleetCount = fleetCount + + self.padding = 4 + + self.fontBig = wx.FontFromPixelSize((0, 15), wx.SWISS, wx.NORMAL, wx.BOLD, False) + self.fontNormal = wx.FontFromPixelSize((0, 14), wx.SWISS, wx.NORMAL, wx.NORMAL, False) + self.fontSmall = wx.FontFromPixelSize((0, 12), wx.SWISS, wx.NORMAL, wx.NORMAL, False) + + self.copyBmp = BitmapLoader.getBitmap("fit_add_small", "gui") + self.renameBmp = BitmapLoader.getBitmap("fit_rename_small", "gui") + self.deleteBmp = BitmapLoader.getBitmap("fit_delete_small", "gui") + self.acceptBmp = BitmapLoader.getBitmap("faccept_small", "gui") + self.fleetBmp = BitmapLoader.getBitmap("fleet_item_big", "gui") + + fleetImg = self.fleetBmp.ConvertToImage() + fleetImg = fleetImg.Blur(2) + + if not fleetImg.HasAlpha(): + fleetImg.InitAlpha() + + fleetImg = fleetImg.AdjustChannels(1, 1, 1, 0.5) + self.fleetEffBmp = wx.BitmapFromImage(fleetImg) + + self.toolbar.AddButton(self.copyBmp, "Copy", self.CopyFleetCB) + self.renameBtn = self.toolbar.AddButton(self.renameBmp, "Rename", self.RenameFleetCB) + self.toolbar.AddButton(self.deleteBmp, "Delete", self.DeleteFleetCB) + + self.editWidth = 150 + self.tcFleetName = wx.TextCtrl(self, wx.ID_ANY, "%s" % self.fleetName, wx.DefaultPosition, (self.editWidth, -1), + wx.TE_PROCESS_ENTER) + + if self.fleetBrowser.fleetIDMustEditName != self.fleetID: + self.tcFleetName.Show(False) + else: + self.tcFleetName.SetFocus() + self.tcFleetName.SelectAll() + self.fleetBrowser.fleetIDMustEditName = -1 + self.renameBtn.SetBitmap(self.acceptBmp) + self.selected = True + + self.tcFleetName.Bind(wx.EVT_KILL_FOCUS, self.OnEditLostFocus) + self.tcFleetName.Bind(wx.EVT_TEXT_ENTER, self.RenameFleet) + self.tcFleetName.Bind(wx.EVT_KEY_DOWN, self.EditCheckEsc) + + self.animCount = 0 + + def MouseLeftUp(self, event): + if self.tcFleetName.IsShown(): + self.RestoreEditButton() + else: + wx.PostEvent(self.fleetBrowser, FleetItemSelect(fleetID=self.fleetID)) + + def CopyFleetCB(self): + if self.tcFleetName.IsShown(): + self.RestoreEditButton() + return + + wx.PostEvent(self.fleetBrowser, FleetItemCopy(fleetID=self.fleetID)) + + def RenameFleetCB(self): + + if self.tcFleetName.IsShown(): + + self.RenameFleet(None) + self.RestoreEditButton() + + else: + self.tcFleetName.SetValue(self.fleetName) + self.tcFleetName.Show() + + self.renameBtn.SetBitmap(self.acceptBmp) + self.Refresh() + + self.tcFleetName.SetFocus() + self.tcFleetName.SelectAll() + + self.Refresh() + + def RenameFleet(self, event): + + newFleetName = self.tcFleetName.GetValue() + self.fleetName = newFleetName + + self.tcFleetName.Show(False) + + wx.PostEvent(self.fleetBrowser, FleetItemRename(fleetID=self.fleetID, fleetName=self.fleetName)) + self.Refresh() + + def DeleteFleetCB(self): + if self.tcFleetName.IsShown(): + self.RestoreEditButton() + return + wx.PostEvent(self.fleetBrowser, FleetItemDelete(fleetID=self.fleetID)) + + def RestoreEditButton(self): + self.tcFleetName.Show(False) + self.renameBtn.SetBitmap(self.renameBmp) + self.Refresh() + + def OnEditLostFocus(self, event): + self.RestoreEditButton() + self.Refresh() + + def EditCheckEsc(self, event): + if event.GetKeyCode() == wx.WXK_ESCAPE: + self.RestoreEditButton() + else: + event.Skip() + + def IsSelected(self): + return self.selected + + def UpdateElementsPos(self, mdc): + rect = self.GetRect() + + self.toolbarx = rect.width - self.toolbar.GetWidth() - self.padding + self.toolbary = (rect.height - self.toolbar.GetHeight()) / 2 + + self.toolbarx = self.toolbarx + self.animCount + + self.fleetBmpx = self.padding + (rect.height - self.fleetBmp.GetWidth()) / 2 + self.fleetBmpy = (rect.height - self.fleetBmp.GetHeight()) / 2 + + self.fleetBmpx -= self.animCount + + self.textStartx = self.fleetBmpx + self.fleetBmp.GetWidth() + self.padding + + self.fleetNamey = (rect.height - self.fleetBmp.GetHeight()) / 2 + + mdc.SetFont(self.fontBig) + wtext, htext = mdc.GetTextExtent(self.fleetName) + + self.fleetCounty = self.fleetNamey + htext + + mdc.SetFont(self.fontSmall) + + wlabel, hlabel = mdc.GetTextExtent(self.toolbar.hoverLabel) + + self.thoverx = self.toolbarx - self.padding - wlabel + self.thovery = (rect.height - hlabel) / 2 + self.thoverw = wlabel + + def DrawItem(self, mdc): + # rect = self.GetRect() + + windowColor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW) + textColor = colorUtils.GetSuitableColor(windowColor, 1) + + mdc.SetTextForeground(textColor) + + self.UpdateElementsPos(mdc) + + self.toolbar.SetPosition((self.toolbarx, self.toolbary)) + mdc.DrawBitmap(self.fleetEffBmp, self.fleetBmpx + 3, self.fleetBmpy + 2) + mdc.DrawBitmap(self.fleetBmp, self.fleetBmpx, self.fleetBmpy) + + mdc.SetFont(self.fontNormal) + + suffix = "%d ships" % self.fleetCount if self.fleetCount > 1 else "%d ship" % self.fleetCount if self.fleetCount == 1 else "No ships" + fleetCount = "Fleet size: %s" % suffix + fleetCount = GetPartialText(mdc, fleetCount, self.toolbarx - self.textStartx - self.padding * 2 - self.thoverw) + + mdc.DrawText(fleetCount, self.textStartx, self.fleetCounty) + + mdc.SetFont(self.fontSmall) + mdc.DrawText(self.toolbar.hoverLabel, self.thoverx, self.thovery) + + mdc.SetFont(self.fontBig) + + pfname = GetPartialText(mdc, self.fleetName, self.toolbarx - self.textStartx - self.padding * 2 - self.thoverw) + mdc.DrawText(pfname, self.textStartx, self.fleetNamey) + + if self.tcFleetName.IsShown(): + self.AdjustControlSizePos(self.tcFleetName, self.textStartx, self.toolbarx - self.editWidth - self.padding) + + def AdjustControlSizePos(self, editCtl, start, end): + fnEditSize = editCtl.GetSize() + wSize = self.GetSize() + fnEditPosX = end + fnEditPosY = (wSize.height - fnEditSize.height) / 2 + if fnEditPosX < start: + editCtl.SetSize((self.editWidth + fnEditPosX - start, -1)) + editCtl.SetPosition((start, fnEditPosY)) + else: + editCtl.SetSize((self.editWidth, -1)) + editCtl.SetPosition((fnEditPosX, fnEditPosY)) + + +class PFGenBitmapButton(GenBitmapButton): + def __init__(self, parent, id, bitmap, pos, size, style): + GenBitmapButton.__init__(self, parent, id, bitmap, pos, size, style) + self.bgcolor = wx.Brush(wx.WHITE) + + def SetBackgroundColour(self, color): + self.bgcolor = wx.Brush(color) + + def GetBackgroundBrush(self, dc): + return self.bgcolor diff --git a/gui/gangView.py b/gui/gangView.py index ea422bc83..93e48c117 100644 --- a/gui/gangView.py +++ b/gui/gangView.py @@ -32,7 +32,8 @@ import gui.globalEvents as GE class GangView(ScrolledPanel): def __init__(self, parent): - ScrolledPanel.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.Size(100, 20), style=wx.TAB_TRAVERSAL | wx.HSCROLL | wx.VSCROLL) + ScrolledPanel.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.Size(100, 20), + style=wx.TAB_TRAVERSAL | wx.HSCROLL | wx.VSCROLL) mainSizer = wx.BoxSizer(wx.VERTICAL) self.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) @@ -49,10 +50,10 @@ class GangView(ScrolledPanel): self.fleet = {} for id_, option in enumerate(self.options): - # set content for each commander self.fleet[id_] = {} - self.fleet[id_]['stLabel'] = wx.StaticText(self, wx.ID_ANY, self.options[id_] + ':', wx.DefaultPosition, wx.DefaultSize, 0) + self.fleet[id_]['stLabel'] = wx.StaticText(self, wx.ID_ANY, self.options[id_] + ':', wx.DefaultPosition, + wx.DefaultSize, 0) self.fleet[id_]['stText'] = wx.StaticText(self, wx.ID_ANY, 'None', wx.DefaultPosition, wx.DefaultSize, 0) self.fleet[id_]['chFit'] = wx.Choice(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, []) self.fleet[id_]['chChar'] = wx.Choice(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, []) @@ -94,7 +95,8 @@ class GangView(ScrolledPanel): for id_ in self.fleet: # set various properties self.fleet[id_]['stLabel'].Wrap(-1) - self.fleet[id_]['stLabel'].SetFont(wx.Font(wx.NORMAL_FONT.GetPointSize(), 70, 90, 92, False, wx.EmptyString)) + self.fleet[id_]['stLabel'].SetFont( + wx.Font(wx.NORMAL_FONT.GetPointSize(), 70, 90, 92, False, wx.EmptyString)) self.fleet[id_]['stText'].Wrap(-1) # bind text and choice events @@ -107,7 +109,8 @@ class GangView(ScrolledPanel): # add fit text and choice to the fit sizer self.fleet[id_]['fitSizer'].Add(self.fleet[id_]['stText'], 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5) - self.fleet[id_]['fitSizer'].Add(self.fleet[id_]['chFit'], 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL | wx.EXPAND, 1) + self.fleet[id_]['fitSizer'].Add(self.fleet[id_]['chFit'], 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL | wx.EXPAND, + 1) # add everything to the content sizer contentFGSizer.Add(self.fleet[id_]['stLabel'], 0, wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5) @@ -271,7 +274,8 @@ class GangView(ScrolledPanel): raise Exception() self.fleet[id_]['stText'].SetLabel(commander.ship.item.name + ": " + commander.name) - self.fleet[id_]['chChar'].SetStringSelection(commander.character.name if commander.character is not None else "All 0") + self.fleet[id_]['chChar'].SetStringSelection( + commander.character.name if commander.character is not None else "All 0") self.fleet[id_]['chChar'].Enable() self.fleet[id_]['chFit'].Hide() self.fleet[id_]['stText'].Show() diff --git a/gui/globalEvents.py b/gui/globalEvents.py index 7b81b3218..18f91d348 100644 --- a/gui/globalEvents.py +++ b/gui/globalEvents.py @@ -1,6 +1,5 @@ import wx.lib.newevent - FitChanged, FIT_CHANGED = wx.lib.newevent.NewEvent() CharListUpdated, CHAR_LIST_UPDATED = wx.lib.newevent.NewEvent() CharChanged, CHAR_CHANGED = wx.lib.newevent.NewEvent() diff --git a/gui/graphFrame.py b/gui/graphFrame.py index dc286af1b..993998d10 100644 --- a/gui/graphFrame.py +++ b/gui/graphFrame.py @@ -1,281 +1,282 @@ -# ============================================================================= -# 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 os - -import wx - -from service.fit import Fit -import gui.display -import gui.mainFrame -import gui.globalEvents as GE -from gui.graph import Graph -from gui.bitmapLoader import BitmapLoader - - -enabled = True -mplImported = False - - -class GraphFrame(wx.Frame): - def __init__(self, parent, style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE | wx.FRAME_FLOAT_ON_PARENT): - - global enabled - global mplImported - - self.legendFix = False - if not enabled: - return - - try: - import matplotlib as mpl - - try: - cache_dir = mpl._get_cachedir() - except: - cache_dir = unicode(os.path.expanduser(os.path.join("~", ".matplotlib"))) - - cache_file = os.path.join(cache_dir, 'fontList.cache') - if os.access(cache_dir, os.W_OK | os.X_OK) and os.path.isfile(cache_file): - # remove matplotlib font cache, see #234 - os.remove(cache_file) - if not mplImported: - mpl.use('wxagg') - from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as Canvas - from matplotlib.figure import Figure - enabled = True - if mpl.__version__[0] != "1": - print("pyfa: Found matplotlib version ", mpl.__version__, " - activating OVER9000 workarounds") - print("pyfa: Recommended minimum matplotlib version is 1.0.0") - self.legendFix = True - except: - print("Problems importing matplotlib; continuing without graphs") - enabled = False - return - - mplImported = True - - wx.Frame.__init__(self, parent, title=u"pyfa: Graph Generator", style=style, size=(520, 390)) - - i = wx.IconFromBitmap(BitmapLoader.getBitmap("graphs_small", "gui")) - self.SetIcon(i) - self.mainFrame = gui.mainFrame.MainFrame.getInstance() - self.CreateStatusBar() - - self.mainSizer = wx.BoxSizer(wx.VERTICAL) - self.SetSizer(self.mainSizer) - - sFit = Fit.getInstance() - fit = sFit.getFit(self.mainFrame.getActiveFit()) - self.fits = [fit] if fit is not None else [] - self.fitList = FitList(self) - self.fitList.SetMinSize((270, -1)) - - self.fitList.fitList.update(self.fits) - - self.graphSelection = wx.Choice(self, wx.ID_ANY, style=0) - self.mainSizer.Add(self.graphSelection, 0, wx.EXPAND) - - self.figure = Figure(figsize=(4, 3)) - - rgbtuple = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE).Get() - clr = [c / 255. for c in rgbtuple] - self.figure.set_facecolor(clr) - self.figure.set_edgecolor(clr) - - self.canvas = Canvas(self, -1, self.figure) - self.canvas.SetBackgroundColour(wx.Colour(*rgbtuple)) - - self.subplot = self.figure.add_subplot(111) - self.subplot.grid(True) - - self.mainSizer.Add(self.canvas, 1, wx.EXPAND) - self.mainSizer.Add(wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0, wx.EXPAND) - - self.gridPanel = wx.Panel(self) - self.mainSizer.Add(self.gridPanel, 0, wx.EXPAND) - - dummyBox = wx.BoxSizer(wx.VERTICAL) - self.gridPanel.SetSizer(dummyBox) - - self.gridSizer = wx.FlexGridSizer(0, 4) - self.gridSizer.AddGrowableCol(1) - dummyBox.Add(self.gridSizer, 0, wx.EXPAND) - - for view in Graph.views: - view = view() - self.graphSelection.Append(view.name, view) - - self.graphSelection.SetSelection(0) - self.fields = {} - self.select(0) - self.sl1 = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) - self.mainSizer.Add(self.sl1, 0, wx.EXPAND) - self.mainSizer.Add(self.fitList, 0, wx.EXPAND) - - self.fitList.fitList.Bind(wx.EVT_LEFT_DCLICK, self.removeItem) - self.mainFrame.Bind(GE.FIT_CHANGED, self.draw) - self.Bind(wx.EVT_CLOSE, self.close) - - self.Fit() - self.SetMinSize(self.GetSize()) - - def handleDrag(self, type, fitID): - if type == "fit": - self.AppendFitToList(fitID) - - def close(self, event): - self.fitList.fitList.Unbind(wx.EVT_LEFT_DCLICK, handler=self.removeItem) - self.mainFrame.Unbind(GE.FIT_CHANGED, handler=self.draw) - event.Skip() - - def getView(self): - return self.graphSelection.GetClientData(self.graphSelection.GetSelection()) - - def getValues(self): - values = {} - for fieldName, field in self.fields.iteritems(): - values[fieldName] = field.GetValue() - - return values - - def select(self, index): - view = self.getView() - icons = view.getIcons() - labels = view.getLabels() - sizer = self.gridSizer - self.gridPanel.DestroyChildren() - self.fields.clear() - - # Setup textboxes - for field, defaultVal in view.getFields().iteritems(): - - textBox = wx.TextCtrl(self.gridPanel, wx.ID_ANY, style=0) - self.fields[field] = textBox - textBox.Bind(wx.EVT_TEXT, self.onFieldChanged) - sizer.Add(textBox, 1, wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 3) - if defaultVal is not None: - if not isinstance(defaultVal, basestring): - defaultVal = ("%f" % defaultVal).rstrip("0") - if defaultVal[-1:] == ".": - defaultVal = defaultVal + "0" - - textBox.ChangeValue(defaultVal) - - imgLabelSizer = wx.BoxSizer(wx.HORIZONTAL) - if icons: - icon = icons.get(field) - if icon is not None: - static = wx.StaticBitmap(self.gridPanel) - static.SetBitmap(icon) - imgLabelSizer.Add(static, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 1) - - if labels: - label = labels.get(field) - label = label if label is not None else field - else: - label = field - - imgLabelSizer.Add(wx.StaticText(self.gridPanel, wx.ID_ANY, label), 0, wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 3) - sizer.Add(imgLabelSizer, 0, wx.ALIGN_CENTER_VERTICAL) - self.draw() - - def draw(self, event=None): - values = self.getValues() - view = self.getView() - self.subplot.clear() - self.subplot.grid(True) - legend = [] - - for fit in self.fits: - try: - success, status = view.getPoints(fit, values) - if not success: - # TODO: Add a pwetty statys bar to report errors with - self.SetStatusText(status) - return - - x, y = success, status - - self.subplot.plot(x, y) - legend.append(fit.name) - except: - self.SetStatusText("Invalid values in '%s'" % fit.name) - self.canvas.draw() - return - - if self.legendFix and len(legend) > 0: - leg = self.subplot.legend(tuple(legend), "upper right", shadow=False) - for t in leg.get_texts(): - t.set_fontsize('small') - - for l in leg.get_lines(): - l.set_linewidth(1) - - elif not self.legendFix and len(legend) > 0: - leg = self.subplot.legend(tuple(legend), "upper right", shadow=False, frameon=False) - for t in leg.get_texts(): - t.set_fontsize('small') - - for l in leg.get_lines(): - l.set_linewidth(1) - - self.canvas.draw() - self.SetStatusText("") - if event is not None: - event.Skip() - - def onFieldChanged(self, event): - self.draw() - - def AppendFitToList(self, fitID): - sFit = Fit.getInstance() - fit = sFit.getFit(fitID) - if fit not in self.fits: - self.fits.append(fit) - - self.fitList.fitList.update(self.fits) - self.draw() - - def removeItem(self, event): - row, _ = self.fitList.fitList.HitTest(event.Position) - if row != -1: - del self.fits[row] - self.fitList.fitList.update(self.fits) - self.draw() - - -class FitList(wx.Panel): - def __init__(self, parent): - wx.Panel.__init__(self, parent) - self.mainSizer = wx.BoxSizer(wx.VERTICAL) - self.SetSizer(self.mainSizer) - - self.fitList = FitDisplay(self) - self.mainSizer.Add(self.fitList, 1, wx.EXPAND) - fitToolTip = wx.ToolTip("Drag a fit into this list to graph it") - self.fitList.SetToolTip(fitToolTip) - - -class FitDisplay(gui.display.Display): - DEFAULT_COLS = ["Base Icon", - "Base Name"] - - def __init__(self, parent): - gui.display.Display.__init__(self, parent) +# ============================================================================= +# 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 os + +import wx + +from service.fit import Fit +import gui.display +import gui.mainFrame +import gui.globalEvents as GE +from gui.graph import Graph +from gui.bitmapLoader import BitmapLoader + +enabled = True +mplImported = False + + +class GraphFrame(wx.Frame): + def __init__(self, parent, style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE | wx.FRAME_FLOAT_ON_PARENT): + + global enabled + global mplImported + + self.legendFix = False + if not enabled: + return + + try: + import matplotlib as mpl + + try: + cache_dir = mpl._get_cachedir() + except: + cache_dir = unicode(os.path.expanduser(os.path.join("~", ".matplotlib"))) + + cache_file = os.path.join(cache_dir, 'fontList.cache') + if os.access(cache_dir, os.W_OK | os.X_OK) and os.path.isfile(cache_file): + # remove matplotlib font cache, see #234 + os.remove(cache_file) + if not mplImported: + mpl.use('wxagg') + from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as Canvas + from matplotlib.figure import Figure + enabled = True + if mpl.__version__[0] != "1": + print("pyfa: Found matplotlib version ", mpl.__version__, " - activating OVER9000 workarounds") + print("pyfa: Recommended minimum matplotlib version is 1.0.0") + self.legendFix = True + except: + print("Problems importing matplotlib; continuing without graphs") + enabled = False + return + + mplImported = True + + wx.Frame.__init__(self, parent, title=u"pyfa: Graph Generator", style=style, size=(520, 390)) + + i = wx.IconFromBitmap(BitmapLoader.getBitmap("graphs_small", "gui")) + self.SetIcon(i) + self.mainFrame = gui.mainFrame.MainFrame.getInstance() + self.CreateStatusBar() + + self.mainSizer = wx.BoxSizer(wx.VERTICAL) + self.SetSizer(self.mainSizer) + + sFit = Fit.getInstance() + fit = sFit.getFit(self.mainFrame.getActiveFit()) + self.fits = [fit] if fit is not None else [] + self.fitList = FitList(self) + self.fitList.SetMinSize((270, -1)) + + self.fitList.fitList.update(self.fits) + + self.graphSelection = wx.Choice(self, wx.ID_ANY, style=0) + self.mainSizer.Add(self.graphSelection, 0, wx.EXPAND) + + self.figure = Figure(figsize=(4, 3)) + + rgbtuple = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE).Get() + clr = [c / 255. for c in rgbtuple] + self.figure.set_facecolor(clr) + self.figure.set_edgecolor(clr) + + self.canvas = Canvas(self, -1, self.figure) + self.canvas.SetBackgroundColour(wx.Colour(*rgbtuple)) + + self.subplot = self.figure.add_subplot(111) + self.subplot.grid(True) + + self.mainSizer.Add(self.canvas, 1, wx.EXPAND) + self.mainSizer.Add(wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0, + wx.EXPAND) + + self.gridPanel = wx.Panel(self) + self.mainSizer.Add(self.gridPanel, 0, wx.EXPAND) + + dummyBox = wx.BoxSizer(wx.VERTICAL) + self.gridPanel.SetSizer(dummyBox) + + self.gridSizer = wx.FlexGridSizer(0, 4) + self.gridSizer.AddGrowableCol(1) + dummyBox.Add(self.gridSizer, 0, wx.EXPAND) + + for view in Graph.views: + view = view() + self.graphSelection.Append(view.name, view) + + self.graphSelection.SetSelection(0) + self.fields = {} + self.select(0) + self.sl1 = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) + self.mainSizer.Add(self.sl1, 0, wx.EXPAND) + self.mainSizer.Add(self.fitList, 0, wx.EXPAND) + + self.fitList.fitList.Bind(wx.EVT_LEFT_DCLICK, self.removeItem) + self.mainFrame.Bind(GE.FIT_CHANGED, self.draw) + self.Bind(wx.EVT_CLOSE, self.close) + + self.Fit() + self.SetMinSize(self.GetSize()) + + def handleDrag(self, type, fitID): + if type == "fit": + self.AppendFitToList(fitID) + + def close(self, event): + self.fitList.fitList.Unbind(wx.EVT_LEFT_DCLICK, handler=self.removeItem) + self.mainFrame.Unbind(GE.FIT_CHANGED, handler=self.draw) + event.Skip() + + def getView(self): + return self.graphSelection.GetClientData(self.graphSelection.GetSelection()) + + def getValues(self): + values = {} + for fieldName, field in self.fields.iteritems(): + values[fieldName] = field.GetValue() + + return values + + def select(self, index): + view = self.getView() + icons = view.getIcons() + labels = view.getLabels() + sizer = self.gridSizer + self.gridPanel.DestroyChildren() + self.fields.clear() + + # Setup textboxes + for field, defaultVal in view.getFields().iteritems(): + + textBox = wx.TextCtrl(self.gridPanel, wx.ID_ANY, style=0) + self.fields[field] = textBox + textBox.Bind(wx.EVT_TEXT, self.onFieldChanged) + sizer.Add(textBox, 1, wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 3) + if defaultVal is not None: + if not isinstance(defaultVal, basestring): + defaultVal = ("%f" % defaultVal).rstrip("0") + if defaultVal[-1:] == ".": + defaultVal = defaultVal + "0" + + textBox.ChangeValue(defaultVal) + + imgLabelSizer = wx.BoxSizer(wx.HORIZONTAL) + if icons: + icon = icons.get(field) + if icon is not None: + static = wx.StaticBitmap(self.gridPanel) + static.SetBitmap(icon) + imgLabelSizer.Add(static, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 1) + + if labels: + label = labels.get(field) + label = label if label is not None else field + else: + label = field + + imgLabelSizer.Add(wx.StaticText(self.gridPanel, wx.ID_ANY, label), 0, + wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 3) + sizer.Add(imgLabelSizer, 0, wx.ALIGN_CENTER_VERTICAL) + self.draw() + + def draw(self, event=None): + values = self.getValues() + view = self.getView() + self.subplot.clear() + self.subplot.grid(True) + legend = [] + + for fit in self.fits: + try: + success, status = view.getPoints(fit, values) + if not success: + # TODO: Add a pwetty statys bar to report errors with + self.SetStatusText(status) + return + + x, y = success, status + + self.subplot.plot(x, y) + legend.append(fit.name) + except: + self.SetStatusText("Invalid values in '%s'" % fit.name) + self.canvas.draw() + return + + if self.legendFix and len(legend) > 0: + leg = self.subplot.legend(tuple(legend), "upper right", shadow=False) + for t in leg.get_texts(): + t.set_fontsize('small') + + for l in leg.get_lines(): + l.set_linewidth(1) + + elif not self.legendFix and len(legend) > 0: + leg = self.subplot.legend(tuple(legend), "upper right", shadow=False, frameon=False) + for t in leg.get_texts(): + t.set_fontsize('small') + + for l in leg.get_lines(): + l.set_linewidth(1) + + self.canvas.draw() + self.SetStatusText("") + if event is not None: + event.Skip() + + def onFieldChanged(self, event): + self.draw() + + def AppendFitToList(self, fitID): + sFit = Fit.getInstance() + fit = sFit.getFit(fitID) + if fit not in self.fits: + self.fits.append(fit) + + self.fitList.fitList.update(self.fits) + self.draw() + + def removeItem(self, event): + row, _ = self.fitList.fitList.HitTest(event.Position) + if row != -1: + del self.fits[row] + self.fitList.fitList.update(self.fits) + self.draw() + + +class FitList(wx.Panel): + def __init__(self, parent): + wx.Panel.__init__(self, parent) + self.mainSizer = wx.BoxSizer(wx.VERTICAL) + self.SetSizer(self.mainSizer) + + self.fitList = FitDisplay(self) + self.mainSizer.Add(self.fitList, 1, wx.EXPAND) + fitToolTip = wx.ToolTip("Drag a fit into this list to graph it") + self.fitList.SetToolTip(fitToolTip) + + +class FitDisplay(gui.display.Display): + DEFAULT_COLS = ["Base Icon", + "Base Name"] + + def __init__(self, parent): + gui.display.Display.__init__(self, parent) diff --git a/gui/itemStats.py b/gui/itemStats.py index fe56f3a09..a7c008b12 100644 --- a/gui/itemStats.py +++ b/gui/itemStats.py @@ -64,7 +64,8 @@ class ItemStatsDialog(wx.Dialog): itmContext = fullContext[1] except IndexError: itmContext = None - item = getattr(victim, "item", None) if srcContext.lower() not in ("projectedcharge", "fittingcharge") else getattr(victim, "charge", None) + item = getattr(victim, "item", None) if srcContext.lower() not in ( + "projectedcharge", "fittingcharge") else getattr(victim, "charge", None) if item is None: sMkt = Market.getInstance() item = sMkt.getItem(victim.ID) @@ -76,7 +77,8 @@ class ItemStatsDialog(wx.Dialog): itemImg = BitmapLoader.getBitmap(iconFile, "icons") if itemImg is not None: self.SetIcon(wx.IconFromBitmap(itemImg)) - self.SetTitle("%s: %s%s" % ("%s Stats" % itmContext if itmContext is not None else "Stats", item.name, " (%d)" % item.ID if config.debug else "")) + self.SetTitle("%s: %s%s" % ("%s Stats" % itmContext if itmContext is not None else "Stats", item.name, + " (%d)" % item.ID if config.debug else "")) self.SetMinSize((300, 200)) if "wxGTK" in wx.PlatformInfo: # GTK has huge tab widgets, give it a bit more room @@ -232,7 +234,8 @@ class ItemDescription(wx.Panel): desc = re.sub("<( *)font( *)color( *)=(.*?)>(?P.*?)<( *)/( *)font( *)>", "\g", desc) # Strip URLs desc = re.sub("<( *)a(.*?)>(?P.*?)<( *)/( *)a( *)>", "\g", desc) - desc = "" + desc + "" + desc = "" + desc + "" self.description.SetPage(desc) @@ -240,12 +243,13 @@ class ItemDescription(wx.Panel): self.Layout() -class ItemParams (wx.Panel): +class ItemParams(wx.Panel): def __init__(self, parent, stuff, item, context=None): wx.Panel.__init__(self, parent) mainSizer = wx.BoxSizer(wx.VERTICAL) - self.paramList = AutoListCtrl(self, wx.ID_ANY, style=wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_VRULES | wx.NO_BORDER) + self.paramList = AutoListCtrl(self, wx.ID_ANY, + style=wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_VRULES | wx.NO_BORDER) mainSizer.Add(self.paramList, 1, wx.ALL | wx.EXPAND, 0) self.SetSizer(mainSizer) @@ -263,10 +267,12 @@ class ItemParams (wx.Panel): self.totalAttrsLabel = wx.StaticText(self, wx.ID_ANY, u" ", wx.DefaultPosition, wx.DefaultSize, 0) bSizer.Add(self.totalAttrsLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT) - self.toggleViewBtn = wx.ToggleButton(self, wx.ID_ANY, u"Toggle view mode", wx.DefaultPosition, wx.DefaultSize, 0) + self.toggleViewBtn = wx.ToggleButton(self, wx.ID_ANY, u"Toggle view mode", wx.DefaultPosition, wx.DefaultSize, + 0) bSizer.Add(self.toggleViewBtn, 0, wx.ALIGN_CENTER_VERTICAL) - self.exportStatsBtn = wx.ToggleButton(self, wx.ID_ANY, u"Export Item Stats", wx.DefaultPosition, wx.DefaultSize, 0) + self.exportStatsBtn = wx.ToggleButton(self, wx.ID_ANY, u"Export Item Stats", wx.DefaultPosition, wx.DefaultSize, + 0) bSizer.Add(self.exportStatsBtn, 0, wx.ALIGN_CENTER_VERTICAL) if stuff is not None: @@ -470,7 +476,8 @@ class ItemParams (wx.Panel): trans = {"Inverse Absolute Percent": (lambda: (1 - value) * 100, unitName), "Inversed Modifier Percent": (lambda: (1 - value) * 100, unitName), - "Modifier Percent": (lambda: ("%+.2f" if ((value - 1) * 100) % 1 else "%+d") % ((value - 1) * 100), unitName), + "Modifier Percent": ( + lambda: ("%+.2f" if ((value - 1) * 100) % 1 else "%+d") % ((value - 1) * 100), unitName), "Volume": (lambda: value, u"m\u00B3"), "Sizeclass": (lambda: value, ""), "Absolute Percent": (lambda: (value * 100), unitName), @@ -508,7 +515,8 @@ class ItemCompare(wx.Panel): self.currentSort = None self.sortReverse = False self.item = item - self.items = sorted(items, key=lambda x: x.attributes['metaLevel'].value if 'metaLevel' in x.attributes else None) + self.items = sorted(items, + key=lambda x: x.attributes['metaLevel'].value if 'metaLevel' in x.attributes else None) self.attrs = {} # get a dict of attrName: attrInfo of all unique attributes across all items @@ -661,7 +669,7 @@ class ItemCompare(wx.Panel): trans = {"Inverse Absolute Percent": (lambda: (1 - value) * 100, unitName), "Inversed Modifier Percent": (lambda: (1 - value) * 100, unitName), "Modifier Percent": ( - lambda: ("%+.2f" if ((value - 1) * 100) % 1 else "%+d") % ((value - 1) * 100), unitName), + lambda: ("%+.2f" if ((value - 1) * 100) % 1 else "%+d") % ((value - 1) * 100), unitName), "Volume": (lambda: value, u"m\u00B3"), "Sizeclass": (lambda: value, ""), "Absolute Percent": (lambda: (value * 100), unitName), @@ -719,14 +727,15 @@ class ItemRequirements(wx.Panel): self.skillIdHistory.append(skill.ID) -class ItemEffects (wx.Panel): +class ItemEffects(wx.Panel): def __init__(self, parent, stuff, item): wx.Panel.__init__(self, parent) self.item = item mainSizer = wx.BoxSizer(wx.VERTICAL) - self.effectList = AutoListCtrl(self, wx.ID_ANY, style=wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_VRULES | wx.NO_BORDER) + self.effectList = AutoListCtrl(self, wx.ID_ANY, + style=wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_VRULES | wx.NO_BORDER) mainSizer.Add(self.effectList, 1, wx.ALL | wx.EXPAND, 0) self.SetSizer(mainSizer) @@ -1033,7 +1042,8 @@ class ItemAffectedBy(wx.Panel): else: item = afflictor.item - items[attrName].append((type(afflictor), afflictor, item, modifier, amount, getattr(afflictor, "projected", False))) + items[attrName].append( + (type(afflictor), afflictor, item, modifier, amount, getattr(afflictor, "projected", False))) # Make sure projected fits are on top rootOrder = container.keys() @@ -1234,7 +1244,8 @@ class ItemAffectedBy(wx.Panel): else: penalized = "" - attributes.append((attrName, (displayName if displayName != "" else attrName), attrModifier, attrAmount, penalized, attrIcon)) + attributes.append((attrName, (displayName if displayName != "" else attrName), attrModifier, + attrAmount, penalized, attrIcon)) attrSorted = sorted(attributes, key=lambda attribName: attribName[0]) for attr in attrSorted: @@ -1242,9 +1253,11 @@ class ItemAffectedBy(wx.Panel): if self.showRealNames: display = "%s %s %.2f %s" % (attrName, attrModifier, attrAmount, penalized) - saved = "%s %s %.2f %s" % ((displayName if displayName != "" else attrName), attrModifier, attrAmount, penalized) + saved = "%s %s %.2f %s" % ( + (displayName if displayName != "" else attrName), attrModifier, attrAmount, penalized) else: - display = "%s %s %.2f %s" % ((displayName if displayName != "" else attrName), attrModifier, attrAmount, penalized) + display = "%s %s %.2f %s" % ( + (displayName if displayName != "" else attrName), attrModifier, attrAmount, penalized) saved = "%s %s %.2f %s" % (attrName, attrModifier, attrAmount, penalized) treeitem = self.affectedBy.AppendItem(child, display, attrIcon) diff --git a/gui/mainFrame.py b/gui/mainFrame.py index 69bdbe3e7..8f2cf73e7 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -81,6 +81,7 @@ if 'wxMac' not in wx.PlatformInfo or ('wxMac' in wx.PlatformInfo and wx.VERSION try: from gui.propertyEditor import AttributeEditor + disableOverrideEditor = False except ImportError as e: print("Error loading Attribute Editor: %s.\nAccess to Attribute Editor is disabled." % e.message) @@ -240,7 +241,8 @@ class MainFrame(wx.Frame): def LoadPreviousOpenFits(self): sFit = Fit.getInstance() - self.prevOpenFits = SettingsProvider.getInstance().getSettings("pyfaPrevOpenFits", {"enabled": False, "pyfaOpenFits": []}) + self.prevOpenFits = SettingsProvider.getInstance().getSettings("pyfaPrevOpenFits", + {"enabled": False, "pyfaOpenFits": []}) fits = self.prevOpenFits['pyfaOpenFits'] # Remove any fits that cause exception when fetching (non-existent fits) @@ -259,8 +261,10 @@ class MainFrame(wx.Frame): 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) + 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"] @@ -623,7 +627,8 @@ class MainFrame(wx.Frame): 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") + menu.SetLabel(menu.toggleOverridesId, + "Turn Overrides Off" if ModifiedAttributeDict.OVERRIDES else "Turn Overrides On") def saveChar(self, event): sChr = Character.getInstance() diff --git a/gui/mainMenuBar.py b/gui/mainMenuBar.py index 2de7f4b80..527f701b1 100644 --- a/gui/mainMenuBar.py +++ b/gui/mainMenuBar.py @@ -27,7 +27,6 @@ import gui.graphFrame import gui.globalEvents as GE from gui.bitmapLoader import BitmapLoader - if 'wxMac' not in wx.PlatformInfo or ('wxMac' in wx.PlatformInfo and wx.VERSION >= (3, 0)): from service.crest import CrestModes @@ -158,7 +157,8 @@ class MainMenuBar(wx.MenuBar): helpMenu.Append(wx.ID_ABOUT) if config.debug: - helpMenu.Append(self.mainFrame.widgetInspectMenuID, "Open Widgets Inspect tool", "Open Widgets Inspect tool") + helpMenu.Append(self.mainFrame.widgetInspectMenuID, "Open Widgets Inspect tool", + "Open Widgets Inspect tool") self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged) diff --git a/gui/marketBrowser.py b/gui/marketBrowser.py index 33db5d501..7efefb7a5 100644 --- a/gui/marketBrowser.py +++ b/gui/marketBrowser.py @@ -1,462 +1,461 @@ -# ============================================================================= -# 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 wx -from service.market import Market -from service.attribute import Attribute -import gui.display as d -import gui.PFSearchBox as SBox -from gui.cachingImageList import CachingImageList -from gui.contextMenu import ContextMenu -from gui.bitmapLoader import BitmapLoader - - -ItemSelected, ITEM_SELECTED = wx.lib.newevent.NewEvent() - -RECENTLY_USED_MODULES = -2 -MAX_RECENTLY_USED_MODULES = 20 - - -class MarketBrowser(wx.Panel): - def __init__(self, parent): - wx.Panel.__init__(self, parent) - vbox = wx.BoxSizer(wx.VERTICAL) - self.SetSizer(vbox) - - # Add a search box on top - self.search = SearchBox(self) - vbox.Add(self.search, 0, wx.EXPAND) - - self.splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE) - vbox.Add(self.splitter, 1, wx.EXPAND) - - # Grab market service instance and create child objects - self.sMkt = Market.getInstance() - self.searchMode = False - self.marketView = MarketTree(self.splitter, self) - self.itemView = ItemView(self.splitter, self) - - self.splitter.SplitHorizontally(self.marketView, self.itemView) - self.splitter.SetMinimumPaneSize(250) - - # Setup our buttons for metaGroup selection - # Same fix as for search box on macs, - # need some pixels of extra space or everything clips and is ugly - p = wx.Panel(self) - box = wx.BoxSizer(wx.HORIZONTAL) - p.SetSizer(box) - vbox.Add(p, 0, wx.EXPAND) - self.metaButtons = [] - for name in self.sMkt.META_MAP.keys(): - btn = wx.ToggleButton(p, wx.ID_ANY, name.capitalize(), style=wx.BU_EXACTFIT) - setattr(self, name, btn) - box.Add(btn, 1, wx.ALIGN_CENTER) - btn.Bind(wx.EVT_TOGGLEBUTTON, self.toggleMetaButton) - btn.metaName = name - self.metaButtons.append(btn) - # Make itemview to set toggles according to list contents - self.itemView.setToggles() - - p.SetMinSize((wx.SIZE_AUTO_WIDTH, btn.GetSize()[1] + 5)) - - def toggleMetaButton(self, event): - """Process clicks on toggle buttons""" - ctrl = wx.GetMouseState().CmdDown() - ebtn = event.EventObject - if not ctrl: - for btn in self.metaButtons: - if btn.Enabled: - if btn == ebtn: - btn.SetValue(True) - else: - btn.SetValue(False) - else: - # Note: using the 'wrong' value for clicked button might seem weird, - # But the button is toggled by wx and we should deal with it - activeBtns = set() - for btn in self.metaButtons: - if (btn.GetValue() is True and btn != ebtn) or (btn.GetValue() is False and btn == ebtn): - activeBtns.add(btn) - # Do 'nothing' if we're trying to turn last active button off - if len(activeBtns) == 1 and activeBtns.pop() == ebtn: - # Keep button in the same state - ebtn.SetValue(True) - return - # Leave old unfiltered list contents, just re-filter them and show - self.itemView.filterItemStore() - - def jump(self, item): - self.marketView.jump(item) - - -class SearchBox(SBox.PFSearchBox): - def __init__(self, parent, **kwargs): - SBox.PFSearchBox.__init__(self, parent, **kwargs) - cancelBitmap = BitmapLoader.getBitmap("fit_delete_small", "gui") - searchBitmap = BitmapLoader.getBitmap("fsearch_small", "gui") - self.SetSearchBitmap(searchBitmap) - self.SetCancelBitmap(cancelBitmap) - self.ShowSearchButton() - self.ShowCancelButton() - - -class MarketTree(wx.TreeCtrl): - def __init__(self, parent, marketBrowser): - wx.TreeCtrl.__init__(self, parent, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT) - self.root = self.AddRoot("root") - - self.imageList = CachingImageList(16, 16) - self.SetImageList(self.imageList) - - self.sMkt = marketBrowser.sMkt - self.marketBrowser = marketBrowser - - # Form market tree root - sMkt = self.sMkt - for mktGrp in sMkt.getMarketRoot(): - iconId = self.addImage(sMkt.getIconByMarketGroup(mktGrp)) - childId = self.AppendItem(self.root, mktGrp.name, iconId, data=wx.TreeItemData(mktGrp.ID)) - # All market groups which were never expanded are dummies, here we assume - # that all root market groups are expandable - self.AppendItem(childId, "dummy") - self.SortChildren(self.root) - - # Add recently used modules node - rumIconId = self.addImage("market_small", "gui") - self.AppendItem(self.root, "Recently Used Modules", rumIconId, data=wx.TreeItemData(RECENTLY_USED_MODULES)) - - # Bind our lookup method to when the tree gets expanded - self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.expandLookup) - - def addImage(self, iconFile, location="icons"): - if iconFile is None: - return -1 - return self.imageList.GetImageIndex(iconFile, location) - - def expandLookup(self, event): - """Process market tree expands""" - root = event.Item - child = self.GetFirstChild(root)[0] - # If child of given market group is a dummy - if self.GetItemText(child) == "dummy": - # Delete it - self.Delete(child) - # And add real market group contents - sMkt = self.sMkt - currentMktGrp = sMkt.getMarketGroup(self.GetPyData(root), eager="children") - for childMktGrp in sMkt.getMarketGroupChildren(currentMktGrp): - # If market should have items but it doesn't, do not show it - if sMkt.marketGroupValidityCheck(childMktGrp) is False: - continue - iconId = self.addImage(sMkt.getIconByMarketGroup(childMktGrp)) - try: - childId = self.AppendItem(root, childMktGrp.name, iconId, data=wx.TreeItemData(childMktGrp.ID)) - except: - continue - if sMkt.marketGroupHasTypesCheck(childMktGrp) is False: - self.AppendItem(childId, "dummy") - - self.SortChildren(root) - - def jump(self, item): - """Open market group and meta tab of given item""" - self.marketBrowser.searchMode = False - sMkt = self.sMkt - mg = sMkt.getMarketGroupByItem(item) - metaId = sMkt.getMetaGroupIdByItem(item) - - jumpList = [] - while mg is not None: - jumpList.append(mg.ID) - mg = mg.parent - - for id in sMkt.ROOT_MARKET_GROUPS: - if id in jumpList: - jumpList = jumpList[:jumpList.index(id) + 1] - - item = self.root - for i in range(len(jumpList) - 1, -1, -1): - target = jumpList[i] - child, cookie = self.GetFirstChild(item) - while self.GetItemPyData(child) != target: - child, cookie = self.GetNextChild(item, cookie) - - item = child - self.Expand(item) - - self.SelectItem(item) - self.marketBrowser.itemView.selectionMade(forcedMetaSelect=metaId) - - -class ItemView(d.Display): - DEFAULT_COLS = ["Base Icon", - "Base Name", - "attr:power,,,True", - "attr:cpu,,,True"] - - def __init__(self, parent, marketBrowser): - d.Display.__init__(self, parent) - marketBrowser.Bind(wx.EVT_TREE_SEL_CHANGED, self.selectionMade) - - self.unfilteredStore = set() - self.filteredStore = set() - self.recentlyUsedModules = set() - self.sMkt = marketBrowser.sMkt - self.searchMode = marketBrowser.searchMode - - self.marketBrowser = marketBrowser - self.marketView = marketBrowser.marketView - - # Make sure our search actually does interesting stuff - self.marketBrowser.search.Bind(SBox.EVT_TEXT_ENTER, self.scheduleSearch) - self.marketBrowser.search.Bind(SBox.EVT_SEARCH_BTN, self.scheduleSearch) - self.marketBrowser.search.Bind(SBox.EVT_CANCEL_BTN, self.clearSearch) - self.marketBrowser.search.Bind(SBox.EVT_TEXT, self.scheduleSearch) - - # Make sure WE do interesting stuff too - self.Bind(wx.EVT_CONTEXT_MENU, self.contextMenu) - self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.itemActivated) - self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.startDrag) - - # Make reverse map, used by sorter - self.metaMap = self.makeReverseMetaMap() - - # Fill up recently used modules set - for itemID in self.sMkt.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"]: - self.recentlyUsedModules.add(self.sMkt.getItem(itemID)) - - def startDrag(self, event): - row = self.GetFirstSelected() - - if row != -1: - data = wx.PyTextDataObject() - data.SetText("market:" + str(self.active[row].ID)) - - dropSource = wx.DropSource(self) - dropSource.SetData(data) - dropSource.DoDragDrop() - - def itemActivated(self, event=None): - # Check if something is selected, if so, spawn the menu for it - sel = self.GetFirstSelected() - if sel == -1: - return - - if self.mainFrame.getActiveFit(): - - self.storeRecentlyUsedMarketItem(self.active[sel].ID) - self.recentlyUsedModules = set() - for itemID in self.sMkt.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"]: - self.recentlyUsedModules.add(self.sMkt.getItem(itemID)) - - wx.PostEvent(self.mainFrame, ItemSelected(itemID=self.active[sel].ID)) - - def storeRecentlyUsedMarketItem(self, itemID): - if len(self.sMkt.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"]) > MAX_RECENTLY_USED_MODULES: - self.sMkt.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"].pop(0) - - self.sMkt.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"].append(itemID) - - def selectionMade(self, event=None, forcedMetaSelect=None): - self.marketBrowser.searchMode = False - # Grab the threeview selection and check if it's fine - sel = self.marketView.GetSelection() - if sel.IsOk(): - # Get data field of the selected item (which is a marketGroup ID if anything was selected) - seldata = self.marketView.GetPyData(sel) - if seldata is not None and seldata != RECENTLY_USED_MODULES: - # If market group treeview item doesn't have children (other market groups or dummies), - # then it should have items in it and we want to request them - if self.marketView.ItemHasChildren(sel) is False: - sMkt = self.sMkt - # Get current market group - mg = sMkt.getMarketGroup(seldata, eager=("items", "items.metaGroup")) - # Get all its items - items = sMkt.getItemsByMarketGroup(mg) - else: - items = set() - else: - # If method was called but selection wasn't actually made or we have a hit on recently used modules - if seldata == RECENTLY_USED_MODULES: - items = self.recentlyUsedModules - else: - items = set() - - # Fill store - self.updateItemStore(items) - - # Set toggle buttons / use search mode flag if recently used modules category is selected (in order to have all modules listed and not filtered) - if seldata is not RECENTLY_USED_MODULES: - self.setToggles(forcedMetaSelect=forcedMetaSelect) - else: - self.marketBrowser.searchMode = True - self.setToggles() - - # Update filtered items - self.filterItemStore() - - def updateItemStore(self, items): - self.unfilteredStore = items - - def filterItemStore(self): - sMkt = self.sMkt - selectedMetas = set() - for btn in self.marketBrowser.metaButtons: - if btn.GetValue(): - selectedMetas.update(sMkt.META_MAP[btn.metaName]) - self.filteredStore = sMkt.filterItemsByMeta(self.unfilteredStore, selectedMetas) - self.update(list(self.filteredStore)) - - def setToggles(self, forcedMetaSelect=None): - metaIDs = set() - sMkt = self.sMkt - for item in self.unfilteredStore: - metaIDs.add(sMkt.getMetaGroupIdByItem(item)) - anySelection = False - for btn in self.marketBrowser.metaButtons: - btnMetas = sMkt.META_MAP[btn.metaName] - if len(metaIDs.intersection(btnMetas)) > 0: - btn.Enable(True) - # Select all available buttons if we're searching - if self.marketBrowser.searchMode is True: - btn.SetValue(True) - # Select explicitly requested button - if forcedMetaSelect is not None: - btn.SetValue(True if forcedMetaSelect in btnMetas else False) - else: - btn.Enable(False) - btn.SetValue(False) - if btn.GetValue(): - anySelection = True - # If no buttons are pressed, press first active - if anySelection is False: - for btn in self.marketBrowser.metaButtons: - if btn.Enabled: - btn.SetValue(True) - break - - def scheduleSearch(self, event=None): - search = self.marketBrowser.search.GetLineText(0) - # Make sure we do not count wildcard as search symbol - realsearch = search.replace("*", "") - # Re-select market group if search query has zero length - if len(realsearch) == 0: - self.selectionMade() - return - # Show nothing if query is too short - elif len(realsearch) < 3: - self.clearSearch() - return - - self.marketBrowser.searchMode = True - self.sMkt.searchItems(search, self.populateSearch) - - def clearSearch(self, event=None): - # Wipe item store and update everything to accomodate with it - # If clearSearch was generated by SearchCtrl's Cancel button, clear the content also - - if event: - self.marketBrowser.search.Clear() - - self.marketBrowser.searchMode = False - self.updateItemStore(set()) - self.setToggles() - self.filterItemStore() - - def populateSearch(self, items): - # If we're no longer searching, dump the results - if self.marketBrowser.searchMode is False: - return - self.updateItemStore(items) - self.setToggles() - self.filterItemStore() - - def itemSort(self, item): - sMkt = self.sMkt - catname = sMkt.getCategoryByItem(item).name - try: - mktgrpid = sMkt.getMarketGroupByItem(item).ID - except AttributeError: - mktgrpid = None - print("unable to find market group for", item.name) - parentname = sMkt.getParentItemByItem(item).name - # Get position of market group - metagrpid = sMkt.getMetaGroupIdByItem(item) - metatab = self.metaMap.get(metagrpid) - metalvl = self.metalvls.get(item.ID, 0) - return (catname, mktgrpid, parentname, metatab, metalvl, item.name) - - def contextMenu(self, event): - # Check if something is selected, if so, spawn the menu for it - sel = self.GetFirstSelected() - if sel == -1: - return - - item = self.active[sel] - - sMkt = self.sMkt - sourceContext = "marketItemGroup" if self.marketBrowser.searchMode is False else "marketItemMisc" - itemContext = sMkt.getCategoryByItem(item).name - - menu = ContextMenu.getMenu((item,), (sourceContext, itemContext)) - self.PopupMenu(menu) - - def populate(self, items): - if len(items) > 0: - # Get dictionary with meta level attribute - sAttr = Attribute.getInstance() - attrs = sAttr.getAttributeInfo("metaLevel") - sMkt = self.sMkt - self.metalvls = sMkt.directAttrRequest(items, attrs) - # Clear selection - self.deselectItems() - # Perform sorting, using item's meta levels besides other stuff - items.sort(key=self.itemSort) - # Mark current item list as active - self.active = items - # Show them - d.Display.populate(self, items) - - def refresh(self, items): - if len(items) > 1: - # Get dictionary with meta level attribute - sAttr = Attribute.getInstance() - attrs = sAttr.getAttributeInfo("metaLevel") - sMkt = self.sMkt - self.metalvls = sMkt.directAttrRequest(items, attrs) - # Re-sort stuff - items.sort(key=self.itemSort) - - for i, item in enumerate(items[:9]): - # set shortcut info for first 9 modules - item.marketShortcut = i + 1 - - d.Display.refresh(self, items) - - def makeReverseMetaMap(self): - """ - Form map which tells in which tab items of given metagroup are located - """ - revmap = {} - i = 0 - for mgids in self.sMkt.META_MAP.itervalues(): - for mgid in mgids: - revmap[mgid] = i - i += 1 - return revmap +# ============================================================================= +# 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 wx +from service.market import Market +from service.attribute import Attribute +import gui.display as d +import gui.PFSearchBox as SBox +from gui.cachingImageList import CachingImageList +from gui.contextMenu import ContextMenu +from gui.bitmapLoader import BitmapLoader + +ItemSelected, ITEM_SELECTED = wx.lib.newevent.NewEvent() + +RECENTLY_USED_MODULES = -2 +MAX_RECENTLY_USED_MODULES = 20 + + +class MarketBrowser(wx.Panel): + def __init__(self, parent): + wx.Panel.__init__(self, parent) + vbox = wx.BoxSizer(wx.VERTICAL) + self.SetSizer(vbox) + + # Add a search box on top + self.search = SearchBox(self) + vbox.Add(self.search, 0, wx.EXPAND) + + self.splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE) + vbox.Add(self.splitter, 1, wx.EXPAND) + + # Grab market service instance and create child objects + self.sMkt = Market.getInstance() + self.searchMode = False + self.marketView = MarketTree(self.splitter, self) + self.itemView = ItemView(self.splitter, self) + + self.splitter.SplitHorizontally(self.marketView, self.itemView) + self.splitter.SetMinimumPaneSize(250) + + # Setup our buttons for metaGroup selection + # Same fix as for search box on macs, + # need some pixels of extra space or everything clips and is ugly + p = wx.Panel(self) + box = wx.BoxSizer(wx.HORIZONTAL) + p.SetSizer(box) + vbox.Add(p, 0, wx.EXPAND) + self.metaButtons = [] + for name in self.sMkt.META_MAP.keys(): + btn = wx.ToggleButton(p, wx.ID_ANY, name.capitalize(), style=wx.BU_EXACTFIT) + setattr(self, name, btn) + box.Add(btn, 1, wx.ALIGN_CENTER) + btn.Bind(wx.EVT_TOGGLEBUTTON, self.toggleMetaButton) + btn.metaName = name + self.metaButtons.append(btn) + # Make itemview to set toggles according to list contents + self.itemView.setToggles() + + p.SetMinSize((wx.SIZE_AUTO_WIDTH, btn.GetSize()[1] + 5)) + + def toggleMetaButton(self, event): + """Process clicks on toggle buttons""" + ctrl = wx.GetMouseState().CmdDown() + ebtn = event.EventObject + if not ctrl: + for btn in self.metaButtons: + if btn.Enabled: + if btn == ebtn: + btn.SetValue(True) + else: + btn.SetValue(False) + else: + # Note: using the 'wrong' value for clicked button might seem weird, + # But the button is toggled by wx and we should deal with it + activeBtns = set() + for btn in self.metaButtons: + if (btn.GetValue() is True and btn != ebtn) or (btn.GetValue() is False and btn == ebtn): + activeBtns.add(btn) + # Do 'nothing' if we're trying to turn last active button off + if len(activeBtns) == 1 and activeBtns.pop() == ebtn: + # Keep button in the same state + ebtn.SetValue(True) + return + # Leave old unfiltered list contents, just re-filter them and show + self.itemView.filterItemStore() + + def jump(self, item): + self.marketView.jump(item) + + +class SearchBox(SBox.PFSearchBox): + def __init__(self, parent, **kwargs): + SBox.PFSearchBox.__init__(self, parent, **kwargs) + cancelBitmap = BitmapLoader.getBitmap("fit_delete_small", "gui") + searchBitmap = BitmapLoader.getBitmap("fsearch_small", "gui") + self.SetSearchBitmap(searchBitmap) + self.SetCancelBitmap(cancelBitmap) + self.ShowSearchButton() + self.ShowCancelButton() + + +class MarketTree(wx.TreeCtrl): + def __init__(self, parent, marketBrowser): + wx.TreeCtrl.__init__(self, parent, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT) + self.root = self.AddRoot("root") + + self.imageList = CachingImageList(16, 16) + self.SetImageList(self.imageList) + + self.sMkt = marketBrowser.sMkt + self.marketBrowser = marketBrowser + + # Form market tree root + sMkt = self.sMkt + for mktGrp in sMkt.getMarketRoot(): + iconId = self.addImage(sMkt.getIconByMarketGroup(mktGrp)) + childId = self.AppendItem(self.root, mktGrp.name, iconId, data=wx.TreeItemData(mktGrp.ID)) + # All market groups which were never expanded are dummies, here we assume + # that all root market groups are expandable + self.AppendItem(childId, "dummy") + self.SortChildren(self.root) + + # Add recently used modules node + rumIconId = self.addImage("market_small", "gui") + self.AppendItem(self.root, "Recently Used Modules", rumIconId, data=wx.TreeItemData(RECENTLY_USED_MODULES)) + + # Bind our lookup method to when the tree gets expanded + self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.expandLookup) + + def addImage(self, iconFile, location="icons"): + if iconFile is None: + return -1 + return self.imageList.GetImageIndex(iconFile, location) + + def expandLookup(self, event): + """Process market tree expands""" + root = event.Item + child = self.GetFirstChild(root)[0] + # If child of given market group is a dummy + if self.GetItemText(child) == "dummy": + # Delete it + self.Delete(child) + # And add real market group contents + sMkt = self.sMkt + currentMktGrp = sMkt.getMarketGroup(self.GetPyData(root), eager="children") + for childMktGrp in sMkt.getMarketGroupChildren(currentMktGrp): + # If market should have items but it doesn't, do not show it + if sMkt.marketGroupValidityCheck(childMktGrp) is False: + continue + iconId = self.addImage(sMkt.getIconByMarketGroup(childMktGrp)) + try: + childId = self.AppendItem(root, childMktGrp.name, iconId, data=wx.TreeItemData(childMktGrp.ID)) + except: + continue + if sMkt.marketGroupHasTypesCheck(childMktGrp) is False: + self.AppendItem(childId, "dummy") + + self.SortChildren(root) + + def jump(self, item): + """Open market group and meta tab of given item""" + self.marketBrowser.searchMode = False + sMkt = self.sMkt + mg = sMkt.getMarketGroupByItem(item) + metaId = sMkt.getMetaGroupIdByItem(item) + + jumpList = [] + while mg is not None: + jumpList.append(mg.ID) + mg = mg.parent + + for id in sMkt.ROOT_MARKET_GROUPS: + if id in jumpList: + jumpList = jumpList[:jumpList.index(id) + 1] + + item = self.root + for i in range(len(jumpList) - 1, -1, -1): + target = jumpList[i] + child, cookie = self.GetFirstChild(item) + while self.GetItemPyData(child) != target: + child, cookie = self.GetNextChild(item, cookie) + + item = child + self.Expand(item) + + self.SelectItem(item) + self.marketBrowser.itemView.selectionMade(forcedMetaSelect=metaId) + + +class ItemView(d.Display): + DEFAULT_COLS = ["Base Icon", + "Base Name", + "attr:power,,,True", + "attr:cpu,,,True"] + + def __init__(self, parent, marketBrowser): + d.Display.__init__(self, parent) + marketBrowser.Bind(wx.EVT_TREE_SEL_CHANGED, self.selectionMade) + + self.unfilteredStore = set() + self.filteredStore = set() + self.recentlyUsedModules = set() + self.sMkt = marketBrowser.sMkt + self.searchMode = marketBrowser.searchMode + + self.marketBrowser = marketBrowser + self.marketView = marketBrowser.marketView + + # Make sure our search actually does interesting stuff + self.marketBrowser.search.Bind(SBox.EVT_TEXT_ENTER, self.scheduleSearch) + self.marketBrowser.search.Bind(SBox.EVT_SEARCH_BTN, self.scheduleSearch) + self.marketBrowser.search.Bind(SBox.EVT_CANCEL_BTN, self.clearSearch) + self.marketBrowser.search.Bind(SBox.EVT_TEXT, self.scheduleSearch) + + # Make sure WE do interesting stuff too + self.Bind(wx.EVT_CONTEXT_MENU, self.contextMenu) + self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.itemActivated) + self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.startDrag) + + # Make reverse map, used by sorter + self.metaMap = self.makeReverseMetaMap() + + # Fill up recently used modules set + for itemID in self.sMkt.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"]: + self.recentlyUsedModules.add(self.sMkt.getItem(itemID)) + + def startDrag(self, event): + row = self.GetFirstSelected() + + if row != -1: + data = wx.PyTextDataObject() + data.SetText("market:" + str(self.active[row].ID)) + + dropSource = wx.DropSource(self) + dropSource.SetData(data) + dropSource.DoDragDrop() + + def itemActivated(self, event=None): + # Check if something is selected, if so, spawn the menu for it + sel = self.GetFirstSelected() + if sel == -1: + return + + if self.mainFrame.getActiveFit(): + + self.storeRecentlyUsedMarketItem(self.active[sel].ID) + self.recentlyUsedModules = set() + for itemID in self.sMkt.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"]: + self.recentlyUsedModules.add(self.sMkt.getItem(itemID)) + + wx.PostEvent(self.mainFrame, ItemSelected(itemID=self.active[sel].ID)) + + def storeRecentlyUsedMarketItem(self, itemID): + if len(self.sMkt.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"]) > MAX_RECENTLY_USED_MODULES: + self.sMkt.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"].pop(0) + + self.sMkt.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"].append(itemID) + + def selectionMade(self, event=None, forcedMetaSelect=None): + self.marketBrowser.searchMode = False + # Grab the threeview selection and check if it's fine + sel = self.marketView.GetSelection() + if sel.IsOk(): + # Get data field of the selected item (which is a marketGroup ID if anything was selected) + seldata = self.marketView.GetPyData(sel) + if seldata is not None and seldata != RECENTLY_USED_MODULES: + # If market group treeview item doesn't have children (other market groups or dummies), + # then it should have items in it and we want to request them + if self.marketView.ItemHasChildren(sel) is False: + sMkt = self.sMkt + # Get current market group + mg = sMkt.getMarketGroup(seldata, eager=("items", "items.metaGroup")) + # Get all its items + items = sMkt.getItemsByMarketGroup(mg) + else: + items = set() + else: + # If method was called but selection wasn't actually made or we have a hit on recently used modules + if seldata == RECENTLY_USED_MODULES: + items = self.recentlyUsedModules + else: + items = set() + + # Fill store + self.updateItemStore(items) + + # Set toggle buttons / use search mode flag if recently used modules category is selected (in order to have all modules listed and not filtered) + if seldata is not RECENTLY_USED_MODULES: + self.setToggles(forcedMetaSelect=forcedMetaSelect) + else: + self.marketBrowser.searchMode = True + self.setToggles() + + # Update filtered items + self.filterItemStore() + + def updateItemStore(self, items): + self.unfilteredStore = items + + def filterItemStore(self): + sMkt = self.sMkt + selectedMetas = set() + for btn in self.marketBrowser.metaButtons: + if btn.GetValue(): + selectedMetas.update(sMkt.META_MAP[btn.metaName]) + self.filteredStore = sMkt.filterItemsByMeta(self.unfilteredStore, selectedMetas) + self.update(list(self.filteredStore)) + + def setToggles(self, forcedMetaSelect=None): + metaIDs = set() + sMkt = self.sMkt + for item in self.unfilteredStore: + metaIDs.add(sMkt.getMetaGroupIdByItem(item)) + anySelection = False + for btn in self.marketBrowser.metaButtons: + btnMetas = sMkt.META_MAP[btn.metaName] + if len(metaIDs.intersection(btnMetas)) > 0: + btn.Enable(True) + # Select all available buttons if we're searching + if self.marketBrowser.searchMode is True: + btn.SetValue(True) + # Select explicitly requested button + if forcedMetaSelect is not None: + btn.SetValue(True if forcedMetaSelect in btnMetas else False) + else: + btn.Enable(False) + btn.SetValue(False) + if btn.GetValue(): + anySelection = True + # If no buttons are pressed, press first active + if anySelection is False: + for btn in self.marketBrowser.metaButtons: + if btn.Enabled: + btn.SetValue(True) + break + + def scheduleSearch(self, event=None): + search = self.marketBrowser.search.GetLineText(0) + # Make sure we do not count wildcard as search symbol + realsearch = search.replace("*", "") + # Re-select market group if search query has zero length + if len(realsearch) == 0: + self.selectionMade() + return + # Show nothing if query is too short + elif len(realsearch) < 3: + self.clearSearch() + return + + self.marketBrowser.searchMode = True + self.sMkt.searchItems(search, self.populateSearch) + + def clearSearch(self, event=None): + # Wipe item store and update everything to accomodate with it + # If clearSearch was generated by SearchCtrl's Cancel button, clear the content also + + if event: + self.marketBrowser.search.Clear() + + self.marketBrowser.searchMode = False + self.updateItemStore(set()) + self.setToggles() + self.filterItemStore() + + def populateSearch(self, items): + # If we're no longer searching, dump the results + if self.marketBrowser.searchMode is False: + return + self.updateItemStore(items) + self.setToggles() + self.filterItemStore() + + def itemSort(self, item): + sMkt = self.sMkt + catname = sMkt.getCategoryByItem(item).name + try: + mktgrpid = sMkt.getMarketGroupByItem(item).ID + except AttributeError: + mktgrpid = None + print("unable to find market group for", item.name) + parentname = sMkt.getParentItemByItem(item).name + # Get position of market group + metagrpid = sMkt.getMetaGroupIdByItem(item) + metatab = self.metaMap.get(metagrpid) + metalvl = self.metalvls.get(item.ID, 0) + return (catname, mktgrpid, parentname, metatab, metalvl, item.name) + + def contextMenu(self, event): + # Check if something is selected, if so, spawn the menu for it + sel = self.GetFirstSelected() + if sel == -1: + return + + item = self.active[sel] + + sMkt = self.sMkt + sourceContext = "marketItemGroup" if self.marketBrowser.searchMode is False else "marketItemMisc" + itemContext = sMkt.getCategoryByItem(item).name + + menu = ContextMenu.getMenu((item,), (sourceContext, itemContext)) + self.PopupMenu(menu) + + def populate(self, items): + if len(items) > 0: + # Get dictionary with meta level attribute + sAttr = Attribute.getInstance() + attrs = sAttr.getAttributeInfo("metaLevel") + sMkt = self.sMkt + self.metalvls = sMkt.directAttrRequest(items, attrs) + # Clear selection + self.deselectItems() + # Perform sorting, using item's meta levels besides other stuff + items.sort(key=self.itemSort) + # Mark current item list as active + self.active = items + # Show them + d.Display.populate(self, items) + + def refresh(self, items): + if len(items) > 1: + # Get dictionary with meta level attribute + sAttr = Attribute.getInstance() + attrs = sAttr.getAttributeInfo("metaLevel") + sMkt = self.sMkt + self.metalvls = sMkt.directAttrRequest(items, attrs) + # Re-sort stuff + items.sort(key=self.itemSort) + + for i, item in enumerate(items[:9]): + # set shortcut info for first 9 modules + item.marketShortcut = i + 1 + + d.Display.refresh(self, items) + + def makeReverseMetaMap(self): + """ + Form map which tells in which tab items of given metagroup are located + """ + revmap = {} + i = 0 + for mgids in self.sMkt.META_MAP.itervalues(): + for mgid in mgids: + revmap[mgid] = i + i += 1 + return revmap diff --git a/gui/patternEditor.py b/gui/patternEditor.py index 8005d19aa..9d11a5ab9 100644 --- a/gui/patternEditor.py +++ b/gui/patternEditor.py @@ -164,18 +164,18 @@ class DmgPatternEditorDlg(wx.Dialog): ("Export", wx.ART_FILE_SAVE_AS, "to")) for name, art, direction in importExport: - bitmap = wx.ArtProvider.GetBitmap(art, wx.ART_BUTTON) - btn = wx.BitmapButton(self, wx.ID_ANY, bitmap) + bitmap = wx.ArtProvider.GetBitmap(art, wx.ART_BUTTON) + btn = wx.BitmapButton(self, wx.ID_ANY, bitmap) - btn.SetMinSize(btn.GetSize()) - btn.SetMaxSize(btn.GetSize()) + btn.SetMinSize(btn.GetSize()) + btn.SetMaxSize(btn.GetSize()) - btn.Layout() - setattr(self, name, btn) - btn.Enable(True) - btn.SetToolTipString("%s patterns %s clipboard" % (name, direction)) - footerSizer.Add(btn, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_RIGHT) - btn.Bind(wx.EVT_BUTTON, getattr(self, "{}Patterns".format(name.lower()))) + btn.Layout() + setattr(self, name, btn) + btn.Enable(True) + btn.SetToolTipString("%s patterns %s clipboard" % (name, direction)) + footerSizer.Add(btn, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_RIGHT) + btn.Bind(wx.EVT_BUTTON, getattr(self, "{}Patterns".format(name.lower()))) self.Layout() bsize = self.GetBestSize() @@ -196,10 +196,10 @@ class DmgPatternEditorDlg(wx.Dialog): p = self.entityEditor.getActiveEntity() total = sum(map(lambda attr: getattr(self, "%sEdit" % attr).GetValue(), self.DAMAGE_TYPES)) for type_ in self.DAMAGE_TYPES: - editObj = getattr(self, "%sEdit" % type_) - percObj = getattr(self, "%sPerc" % type_) - setattr(p, "%sAmount" % type_, editObj.GetValue()) - percObj.SetLabel("%.1f%%" % (float(editObj.GetValue()) * 100 / total if total > 0 else 0)) + editObj = getattr(self, "%sEdit" % type_) + percObj = getattr(self, "%sPerc" % type_) + setattr(p, "%sAmount" % type_, editObj.GetValue()) + percObj.SetLabel("%.1f%%" % (float(editObj.GetValue()) * 100 / total if total > 0 else 0)) self.totSizer.Layout() diff --git a/gui/propertyEditor.py b/gui/propertyEditor.py index dd257302d..1129545a6 100644 --- a/gui/propertyEditor.py +++ b/gui/propertyEditor.py @@ -25,7 +25,8 @@ logger = logging.getLogger(__name__) class AttributeEditor(wx.Frame): def __init__(self, parent): wx.Frame.__init__(self, parent, wx.ID_ANY, title="Attribute Editor", pos=wx.DefaultPosition, - size=wx.Size(650, 600), style=wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT | wx.TAB_TRAVERSAL) + size=wx.Size(650, 600), + style=wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT | wx.TAB_TRAVERSAL) i = wx.IconFromBitmap(BitmapLoader.getBitmap("fit_rename_small", "gui")) self.SetIcon(i) @@ -54,7 +55,8 @@ class AttributeEditor(wx.Frame): mainSizer = wx.BoxSizer(wx.HORIZONTAL) leftSizer = wx.BoxSizer(wx.VERTICAL) - leftPanel = wx.Panel(panel, wx.ID_ANY, style=wx.DOUBLE_BORDER if 'wxMSW' in wx.PlatformInfo else wx.SIMPLE_BORDER) + leftPanel = wx.Panel(panel, wx.ID_ANY, + style=wx.DOUBLE_BORDER if 'wxMSW' in wx.PlatformInfo else wx.SIMPLE_BORDER) self.searchBox = SearchBox(leftPanel) self.itemView = ItemView(leftPanel) @@ -66,7 +68,8 @@ class AttributeEditor(wx.Frame): mainSizer.Add(leftPanel, 1, wx.ALL | wx.EXPAND, 5) rightSizer = wx.BoxSizer(wx.VERTICAL) - self.btnRemoveOverrides = wx.Button(panel, wx.ID_ANY, u"Remove Overides for Item", wx.DefaultPosition, wx.DefaultSize, 0) + self.btnRemoveOverrides = wx.Button(panel, wx.ID_ANY, u"Remove Overides for Item", wx.DefaultPosition, + wx.DefaultSize, 0) self.pg = AttributeGrid(panel) rightSizer.Add(self.pg, 1, wx.ALL | wx.EXPAND, 5) rightSizer.Add(self.btnRemoveOverrides, 0, wx.ALL | wx.EXPAND, 5) @@ -202,7 +205,8 @@ class ItemView(d.Display): class AttributeGrid(wxpg.PropertyGrid): def __init__(self, parent): - wxpg.PropertyGrid.__init__(self, parent, style=wxpg.PG_HIDE_MARGIN | wxpg.PG_HIDE_CATEGORIES | wxpg.PG_BOLD_MODIFIED | wxpg.PG_TOOLTIPS) + wxpg.PropertyGrid.__init__(self, parent, + style=wxpg.PG_HIDE_MARGIN | wxpg.PG_HIDE_CATEGORIES | wxpg.PG_BOLD_MODIFIED | wxpg.PG_TOOLTIPS) self.SetExtraStyle(wxpg.PG_EX_HELP_AS_TOOLTIPS) self.item = None diff --git a/gui/pyfatogglepanel.py b/gui/pyfatogglepanel.py index d34985115..db37216ba 100644 --- a/gui/pyfatogglepanel.py +++ b/gui/pyfatogglepanel.py @@ -27,7 +27,8 @@ from gui.bitmapLoader import BitmapLoader class TogglePanel(wx.Panel): def __init__(self, parent, forceLayout=-1): - wx.Panel.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.Size(-1, -1), style=wx.TAB_TRAVERSAL) + wx.Panel.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.Size(-1, -1), + style=wx.TAB_TRAVERSAL) self._toggle = 1 self.parent = parent diff --git a/gui/resistsEditor.py b/gui/resistsEditor.py index 83fb4327d..84f7bf98b 100644 --- a/gui/resistsEditor.py +++ b/gui/resistsEditor.py @@ -79,7 +79,6 @@ class TargetResistsEntityEditor(EntityEditor): class ResistsEditorDlg(wx.Dialog): - DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive") def __init__(self, parent): @@ -120,7 +119,8 @@ class ResistsEditorDlg(wx.Dialog): setattr(self, "%sEdit" % type_, wx.TextCtrl(self, wx.ID_ANY, "", wx.DefaultPosition, defSize)) editObj = getattr(self, "%sEdit" % type_) resistEditSizer.Add(editObj, 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5) - resistEditSizer.Add(wx.StaticText(self, wx.ID_ANY, u"%", wx.DefaultPosition, wx.DefaultSize, 0), 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5) + resistEditSizer.Add(wx.StaticText(self, wx.ID_ANY, u"%", wx.DefaultPosition, wx.DefaultSize, 0), 0, + wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5) editObj.Bind(wx.EVT_TEXT, self.ValuesUpdated) # Color we use to reset invalid value color diff --git a/gui/setEditor.py b/gui/setEditor.py index 380c7986a..0118fbe03 100644 --- a/gui/setEditor.py +++ b/gui/setEditor.py @@ -146,17 +146,17 @@ class ImplantSetEditorDlg(wx.Dialog): ("Export", wx.ART_FILE_SAVE_AS, "to")) for name, art, direction in importExport: - bitmap = wx.ArtProvider.GetBitmap(art, wx.ART_BUTTON) - btn = wx.BitmapButton(self, wx.ID_ANY, bitmap) + bitmap = wx.ArtProvider.GetBitmap(art, wx.ART_BUTTON) + btn = wx.BitmapButton(self, wx.ID_ANY, bitmap) - btn.SetMinSize(btn.GetSize()) - btn.SetMaxSize(btn.GetSize()) + btn.SetMinSize(btn.GetSize()) + btn.SetMaxSize(btn.GetSize()) - btn.Layout() - setattr(self, name, btn) - btn.Enable(True) - btn.SetToolTipString("%s implant sets %s clipboard" % (name, direction)) - footerSizer.Add(btn, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_RIGHT) + btn.Layout() + setattr(self, name, btn) + btn.Enable(True) + btn.SetToolTipString("%s implant sets %s clipboard" % (name, direction)) + footerSizer.Add(btn, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_RIGHT) mainSizer.Add(footerSizer, 0, wx.ALL | wx.EXPAND, 5) diff --git a/gui/sfBrowserItem.py b/gui/sfBrowserItem.py index 9b12fcf01..047c04bc6 100644 --- a/gui/sfBrowserItem.py +++ b/gui/sfBrowserItem.py @@ -13,7 +13,8 @@ BTN_DISABLED = 8 class PFBaseButton(object): - def __init__(self, normalBitmap=wx.NullBitmap, label="", callback=None, hoverBitmap=None, disabledBitmap=None, show=True): + def __init__(self, normalBitmap=wx.NullBitmap, label="", callback=None, hoverBitmap=None, disabledBitmap=None, + show=True): self.normalBmp = normalBitmap self.dropShadowOpacity = 0.2 diff --git a/gui/shipBrowser.py b/gui/shipBrowser.py index 8c27965b9..6a15678e2 100644 --- a/gui/shipBrowser.py +++ b/gui/shipBrowser.py @@ -20,7 +20,6 @@ from gui.PFListPane import PFListPane from gui.contextMenu import ContextMenu from gui.bitmapLoader import BitmapLoader - FitRenamed, EVT_FIT_RENAMED = wx.lib.newevent.NewEvent() FitSelected, EVT_FIT_SELECTED = wx.lib.newevent.NewEvent() FitRemoved, EVT_FIT_REMOVED = wx.lib.newevent.NewEvent() @@ -67,7 +66,8 @@ class PFWidgetsContainer(PFListPane): class RaceSelector(wx.Window): - def __init__(self, parent, id=wx.ID_ANY, label="", pos=wx.DefaultPosition, size=wx.DefaultSize, style=0, layout=wx.VERTICAL, animate=False): + def __init__(self, parent, id=wx.ID_ANY, label="", pos=wx.DefaultPosition, size=wx.DefaultSize, style=0, + layout=wx.VERTICAL, animate=False): wx.Window.__init__(self, parent, id, pos=pos, size=size, style=style) self.animTimerID = wx.NewId() @@ -344,11 +344,16 @@ class NavigationPanel(SFItem.SFBrowserItem): self.switchBmp = self.AdjustChannels(self.switchBmpH) self.newBmp = self.AdjustChannels(self.newBmpH) - self.toolbar.AddButton(self.resetBmp, "Ship groups", clickCallback=self.OnHistoryReset, hoverBitmap=self.resetBmpH) + self.toolbar.AddButton(self.resetBmp, "Ship groups", clickCallback=self.OnHistoryReset, + hoverBitmap=self.resetBmpH) self.toolbar.AddButton(self.rewBmp, "Back", clickCallback=self.OnHistoryBack, hoverBitmap=self.rewBmpH) - self.btnNew = self.toolbar.AddButton(self.newBmp, "New fitting", clickCallback=self.OnNewFitting, hoverBitmap=self.newBmpH, show=False) - self.btnSwitch = self.toolbar.AddButton(self.switchBmpD, "Hide empty ship groups", clickCallback=self.ToggleEmptyGroupsView, hoverBitmap=self.switchBmpH, show=False) - self.toolbar.AddButton(self.searchBmp, "Search fittings", clickCallback=self.ToggleSearchBox, hoverBitmap=self.searchBmpH) + self.btnNew = self.toolbar.AddButton(self.newBmp, "New fitting", clickCallback=self.OnNewFitting, + hoverBitmap=self.newBmpH, show=False) + self.btnSwitch = self.toolbar.AddButton(self.switchBmpD, "Hide empty ship groups", + clickCallback=self.ToggleEmptyGroupsView, hoverBitmap=self.switchBmpH, + show=False) + self.toolbar.AddButton(self.searchBmp, "Search fittings", clickCallback=self.ToggleSearchBox, + hoverBitmap=self.searchBmpH) self.padding = 4 self.lastSearch = "" @@ -357,7 +362,9 @@ class NavigationPanel(SFItem.SFBrowserItem): self.fontSmall = wx.Font(fonts.SMALL, wx.SWISS, wx.NORMAL, wx.NORMAL) w, h = size - self.BrowserSearchBox = wx.TextCtrl(self, wx.ID_ANY, "", wx.DefaultPosition, (-1, h - 2 if 'wxGTK' in wx.PlatformInfo else -1), wx.TE_PROCESS_ENTER | (wx.BORDER_NONE if 'wxGTK' in wx.PlatformInfo else 0)) + self.BrowserSearchBox = wx.TextCtrl(self, wx.ID_ANY, "", wx.DefaultPosition, + (-1, h - 2 if 'wxGTK' in wx.PlatformInfo else -1), + wx.TE_PROCESS_ENTER | (wx.BORDER_NONE if 'wxGTK' in wx.PlatformInfo else 0)) self.BrowserSearchBox.Show(False) self.BrowserSearchBox.Bind(wx.EVT_TEXT_ENTER, self.OnBrowserSearchBoxEnter) @@ -882,7 +889,9 @@ class ShipBrowser(wx.Panel): for ship in ships: shipTrait = ship.traits.traitText if (ship.traits is not None) else "" # empty string if no traits - self.lpane.AddWidget(ShipItem(self.lpane, ship.ID, (ship.name, shipTrait, len(sFit.getFitsWithShip(ship.ID))), ship.race)) + self.lpane.AddWidget( + ShipItem(self.lpane, ship.ID, (ship.name, shipTrait, len(sFit.getFitsWithShip(ship.ID))), + ship.race)) for ID, name, shipID, shipName, booster, timestamp in fitList: ship = sMkt.getItem(shipID) @@ -923,7 +932,8 @@ class ShipBrowser(wx.Panel): if fits: for fit in fits: - shipTrait = fit.ship.traits.traitText if (fit.ship.traits is not None) else "" # empty string if no traits + shipTrait = fit.ship.traits.traitText if ( + fit.ship.traits is not None) else "" # empty string if no traits self.lpane.AddWidget(FitItem( self.lpane, @@ -1067,6 +1077,7 @@ class CategoryItem(SFItem.SFBrowserItem): mdc.DrawText(categoryName, self.catx, self.caty) + # ============================================================================= # Waiting for total #fits impl in eos/service # @@ -1140,7 +1151,8 @@ class ShipItem(SFItem.SFBrowserItem): self.editWidth = 150 self.padding = 4 - self.tcFitName = wx.TextCtrl(self, wx.ID_ANY, "%s fit" % self.shipName, wx.DefaultPosition, (120, -1), wx.TE_PROCESS_ENTER) + self.tcFitName = wx.TextCtrl(self, wx.ID_ANY, "%s fit" % self.shipName, wx.DefaultPosition, (120, -1), + wx.TE_PROCESS_ENTER) self.tcFitName.Show(False) self.newBtn = self.toolbar.AddButton(self.newBmp, "New", self.newBtnCB) @@ -1331,7 +1343,8 @@ class ShipItem(SFItem.SFBrowserItem): mdc.SetFont(self.fontBig) - psname = drawUtils.GetPartialText(mdc, shipName, self.toolbarx - self.textStartx - self.padding * 2 - self.thoverw) + psname = drawUtils.GetPartialText(mdc, shipName, + self.toolbarx - self.textStartx - self.padding * 2 - self.thoverw) mdc.DrawText(psname, self.textStartx, self.shipNamey) @@ -1353,7 +1366,8 @@ class ShipItem(SFItem.SFBrowserItem): class PFBitmapFrame(wx.Frame): def __init__(self, parent, pos, bitmap): - wx.Frame.__init__(self, parent, id=wx.ID_ANY, title=wx.EmptyString, pos=pos, size=wx.DefaultSize, style=wx.NO_BORDER | wx.FRAME_NO_TASKBAR | wx.STAY_ON_TOP) + wx.Frame.__init__(self, parent, id=wx.ID_ANY, title=wx.EmptyString, pos=pos, size=wx.DefaultSize, + style=wx.NO_BORDER | wx.FRAME_NO_TASKBAR | wx.STAY_ON_TOP) img = bitmap.ConvertToImage() img = img.ConvertToGreyscale() bitmap = wx.BitmapFromImage(img) @@ -1409,7 +1423,8 @@ class PFBitmapFrame(wx.Frame): class FitItem(SFItem.SFBrowserItem): - def __init__(self, parent, fitID=None, shipFittingInfo=("Test", "TestTrait", "cnc's avatar", 0, 0), shipID=None, itemData=None, + def __init__(self, parent, fitID=None, shipFittingInfo=("Test", "TestTrait", "cnc's avatar", 0, 0), shipID=None, + itemData=None, id=wx.ID_ANY, pos=wx.DefaultPosition, size=(0, 40), style=0): @@ -1487,7 +1502,8 @@ class FitItem(SFItem.SFBrowserItem): self.renameBtn = self.toolbar.AddButton(self.renameBmp, "Rename", self.renameBtnCB) self.toolbar.AddButton(self.deleteBmp, "Delete", self.deleteBtnCB) - self.tcFitName = wx.TextCtrl(self, wx.ID_ANY, "%s" % self.fitName, wx.DefaultPosition, (self.editWidth, -1), wx.TE_PROCESS_ENTER) + self.tcFitName = wx.TextCtrl(self, wx.ID_ANY, "%s" % self.fitName, wx.DefaultPosition, (self.editWidth, -1), + wx.TE_PROCESS_ENTER) if self.shipBrowser.fitIDMustEditName != self.fitID: self.tcFitName.Show(False) @@ -1852,7 +1868,8 @@ class FitItem(SFItem.SFBrowserItem): fitDate = time.localtime(self.timestamp) fitLocalDate = "%d/%02d/%02d %02d:%02d" % (fitDate[0], fitDate[1], fitDate[2], fitDate[3], fitDate[4]) - pfdate = drawUtils.GetPartialText(mdc, fitLocalDate, self.toolbarx - self.textStartx - self.padding * 2 - self.thoverw) + pfdate = drawUtils.GetPartialText(mdc, fitLocalDate, + self.toolbarx - self.textStartx - self.padding * 2 - self.thoverw) mdc.DrawText(pfdate, self.textStartx, self.timestampy) @@ -1861,7 +1878,8 @@ class FitItem(SFItem.SFBrowserItem): mdc.SetFont(self.fontBig) - psname = drawUtils.GetPartialText(mdc, self.fitName, self.toolbarx - self.textStartx - self.padding * 2 - self.thoverw) + psname = drawUtils.GetPartialText(mdc, self.fitName, + self.toolbarx - self.textStartx - self.padding * 2 - self.thoverw) mdc.DrawText(psname, self.textStartx, self.fitNamey) diff --git a/gui/statsPane.py b/gui/statsPane.py index 9645f06dd..5d20f22ef 100644 --- a/gui/statsPane.py +++ b/gui/statsPane.py @@ -81,7 +81,8 @@ class StatsPane(wx.Panel): mainSizer.Add(tp, 0, wx.EXPAND | wx.LEFT, 3) if i < maxviews - 1: - mainSizer.Add(wx.StaticLine(self, wx.ID_ANY, style=wx.HORIZONTAL), 0, wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, 2) + mainSizer.Add(wx.StaticLine(self, wx.ID_ANY, style=wx.HORIZONTAL), 0, + wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, 2) i += 1 tp.OnStateChange(tp.GetBestSize()) diff --git a/gui/updateDialog.py b/gui/updateDialog.py index 7b148c546..8a88f9e17 100644 --- a/gui/updateDialog.py +++ b/gui/updateDialog.py @@ -23,79 +23,89 @@ import config import dateutil.parser from service import settings -class UpdateDialog(wx.Dialog): +class UpdateDialog(wx.Dialog): def __init__(self, parent, release): - wx.Dialog.__init__ ( self, parent, id = wx.ID_ANY, title = "Pyfa Update", pos = wx.DefaultPosition, size = wx.Size( 400,300 ), style = wx.DEFAULT_DIALOG_STYLE ) + wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title="Pyfa Update", pos=wx.DefaultPosition, + size=wx.Size(400, 300), style=wx.DEFAULT_DIALOG_STYLE) self.UpdateSettings = settings.UpdateSettings.getInstance() self.releaseInfo = release - self.SetSizeHintsSz( wx.DefaultSize, wx.DefaultSize ) + self.SetSizeHintsSz(wx.DefaultSize, wx.DefaultSize) - mainSizer = wx.BoxSizer( wx.VERTICAL ) + mainSizer = wx.BoxSizer(wx.VERTICAL) - headSizer = wx.BoxSizer( wx.HORIZONTAL ) + headSizer = wx.BoxSizer(wx.HORIZONTAL) - self.headingText = wx.StaticText( self, wx.ID_ANY, "Pyfa Update Available!", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE ) - self.headingText.Wrap( -1 ) - self.headingText.SetFont( wx.Font( 14, 74, 90, 92, False) ) + self.headingText = wx.StaticText(self, wx.ID_ANY, "Pyfa Update Available!", wx.DefaultPosition, wx.DefaultSize, + wx.ALIGN_CENTRE) + self.headingText.Wrap(-1) + self.headingText.SetFont(wx.Font(14, 74, 90, 92, False)) - headSizer.Add( self.headingText, 1, wx.ALL, 5 ) - mainSizer.Add( headSizer, 0, wx.EXPAND, 5 ) + headSizer.Add(self.headingText, 1, wx.ALL, 5) + mainSizer.Add(headSizer, 0, wx.EXPAND, 5) - mainSizer.Add( wx.StaticLine( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL ), 0, wx.EXPAND |wx.ALL, 5 ) + mainSizer.Add(wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0, + wx.EXPAND | wx.ALL, 5) - versionSizer = wx.BoxSizer( wx.HORIZONTAL ) + versionSizer = wx.BoxSizer(wx.HORIZONTAL) - if(self.releaseInfo['prerelease']): - self.releaseText = wx.StaticText( self, wx.ID_ANY, "Pre-release", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT ) - self.releaseText.SetFont( wx.Font( 12, 74, 90, 92, False) ) - self.releaseText.SetForegroundColour( wx.Colour( 230, 0, 0 ) ) + if (self.releaseInfo['prerelease']): + self.releaseText = wx.StaticText(self, wx.ID_ANY, "Pre-release", wx.DefaultPosition, wx.DefaultSize, + wx.ALIGN_RIGHT) + self.releaseText.SetFont(wx.Font(12, 74, 90, 92, False)) + self.releaseText.SetForegroundColour(wx.Colour(230, 0, 0)) else: - self.releaseText = wx.StaticText( self, wx.ID_ANY, "Stable", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT ) - self.releaseText.SetFont( wx.Font( 12, 74, 90, 90, False) ) + self.releaseText = wx.StaticText(self, wx.ID_ANY, "Stable", wx.DefaultPosition, wx.DefaultSize, + wx.ALIGN_RIGHT) + self.releaseText.SetFont(wx.Font(12, 74, 90, 90, False)) - self.releaseText.Wrap( -1 ) + self.releaseText.Wrap(-1) - versionSizer.Add( self.releaseText, 1, wx.ALL, 5 ) + versionSizer.Add(self.releaseText, 1, wx.ALL, 5) - self.versionText = wx.StaticText( self, wx.ID_ANY, self.releaseInfo['tag_name'], wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_LEFT ) - self.versionText.Wrap( -1 ) - self.versionText.SetFont( wx.Font( 12, 74, 90, 90, False) ) + self.versionText = wx.StaticText(self, wx.ID_ANY, self.releaseInfo['tag_name'], wx.DefaultPosition, + wx.DefaultSize, wx.ALIGN_LEFT) + self.versionText.Wrap(-1) + self.versionText.SetFont(wx.Font(12, 74, 90, 90, False)) - versionSizer.Add( self.versionText, 1, wx.ALL, 5 ) - versionSizer.AddSpacer( ( 15, 5), 0, wx.EXPAND, 5 ) + versionSizer.Add(self.versionText, 1, wx.ALL, 5) + versionSizer.AddSpacer((15, 5), 0, wx.EXPAND, 5) - mainSizer.Add( versionSizer, 0, wx.EXPAND, 5 ) - mainSizer.AddSpacer( ( 0, 5), 0, wx.EXPAND, 5 ) + mainSizer.Add(versionSizer, 0, wx.EXPAND, 5) + mainSizer.AddSpacer((0, 5), 0, wx.EXPAND, 5) releaseDate = dateutil.parser.parse(self.releaseInfo['published_at']) - notesSizer = wx.BoxSizer( wx.HORIZONTAL ) - self.notesTextCtrl = wx.TextCtrl( self, wx.ID_ANY, str(releaseDate.date())+":\n\n"+self.releaseInfo['body'], wx.DefaultPosition, wx.DefaultSize, wx.TE_AUTO_URL|wx.TE_MULTILINE|wx.TE_READONLY|wx.DOUBLE_BORDER|wx.TRANSPARENT_WINDOW ) + notesSizer = wx.BoxSizer(wx.HORIZONTAL) + self.notesTextCtrl = wx.TextCtrl(self, wx.ID_ANY, str(releaseDate.date()) + ":\n\n" + self.releaseInfo['body'], + wx.DefaultPosition, wx.DefaultSize, + wx.TE_AUTO_URL | wx.TE_MULTILINE | wx.TE_READONLY | wx.DOUBLE_BORDER | wx.TRANSPARENT_WINDOW) - notesSizer.Add( self.notesTextCtrl, 1, wx.EXPAND|wx.LEFT|wx.RIGHT, 5 ) - mainSizer.Add( notesSizer, 1, wx.EXPAND, 5 ) + notesSizer.Add(self.notesTextCtrl, 1, wx.EXPAND | wx.LEFT | wx.RIGHT, 5) + mainSizer.Add(notesSizer, 1, wx.EXPAND, 5) - self.supressCheckbox = wx.CheckBox( self, wx.ID_ANY, "Don't remind me again for this release", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.supressCheckbox = wx.CheckBox(self, wx.ID_ANY, "Don't remind me again for this release", + wx.DefaultPosition, wx.DefaultSize, 0) self.supressCheckbox.Bind(wx.EVT_CHECKBOX, self.SuppressChange) - mainSizer.Add( self.supressCheckbox, 0, wx.ALL, 5 ) - mainSizer.Add( wx.StaticLine( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL ), 0, wx.EXPAND |wx.ALL, 5 ) + mainSizer.Add(self.supressCheckbox, 0, wx.ALL, 5) + mainSizer.Add(wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0, + wx.EXPAND | wx.ALL, 5) - actionSizer = wx.BoxSizer( wx.HORIZONTAL ) + actionSizer = wx.BoxSizer(wx.HORIZONTAL) - goSizer = wx.BoxSizer( wx.VERTICAL ) - self.downloadButton = wx.Button( self, wx.ID_ANY, "Download", wx.DefaultPosition, wx.DefaultSize, 0 ) + goSizer = wx.BoxSizer(wx.VERTICAL) + self.downloadButton = wx.Button(self, wx.ID_ANY, "Download", wx.DefaultPosition, wx.DefaultSize, 0) self.downloadButton.Bind(wx.EVT_BUTTON, self.OnDownload) - goSizer.Add( self.downloadButton, 0, wx.ALL, 5 ) - actionSizer.Add( goSizer, 1, wx.EXPAND, 5 ) + goSizer.Add(self.downloadButton, 0, wx.ALL, 5) + actionSizer.Add(goSizer, 1, wx.EXPAND, 5) self.closeButton = wx.Button(self, wx.ID_CLOSE) self.closeButton.Bind(wx.EVT_BUTTON, self.OnClose) - actionSizer.Add( self.closeButton, 0, wx.ALL, 5 ) - mainSizer.Add( actionSizer, 0, wx.EXPAND, 5 ) + actionSizer.Add(self.closeButton, 0, wx.ALL, 5) + mainSizer.Add(actionSizer, 0, wx.EXPAND, 5) - self.SetSizer( mainSizer ) + self.SetSizer(mainSizer) self.Layout() # Handle use-case of suppressing a release, then a new version becoming available. @@ -104,7 +114,7 @@ class UpdateDialog(wx.Dialog): # safely reset this setting self.UpdateSettings.set('version', None) - self.Centre( wx.BOTH ) + self.Centre(wx.BOTH) def OnClose(self, e): self.Close() @@ -116,5 +126,5 @@ class UpdateDialog(wx.Dialog): self.UpdateSettings.set('version', None) def OnDownload(self, e): - wx.LaunchDefaultBrowser('https://github.com/pyfa-org/Pyfa/releases/tag/'+self.releaseInfo['tag_name']) + wx.LaunchDefaultBrowser('https://github.com/pyfa-org/Pyfa/releases/tag/' + self.releaseInfo['tag_name']) self.OnClose(e) diff --git a/gui/utils/fonts.py b/gui/utils/fonts.py index 0ee52fb7f..eb2dd95f5 100644 --- a/gui/utils/fonts.py +++ b/gui/utils/fonts.py @@ -5,11 +5,9 @@ different wxPython versions import wx - if 'wxMac' in wx.PlatformInfo: sizes = (10, 11, 12) else: sizes = (7, 8, 9) - SMALL, NORMAL, BIG = sizes diff --git a/service/attribute.py b/service/attribute.py index 12b9b264b..41d10b80a 100644 --- a/service/attribute.py +++ b/service/attribute.py @@ -19,8 +19,10 @@ import eos.db + class Attribute(): instance = None + @classmethod def getInstance(cls): if cls.instance is None: diff --git a/service/character.py b/service/character.py index f93345224..3851c44bb 100644 --- a/service/character.py +++ b/service/character.py @@ -40,6 +40,7 @@ from eos.saveddata.fighter import Fighter as es_Fighter logger = logging.getLogger(__name__) + class CharacterImportThread(threading.Thread): def __init__(self, paths, callback): threading.Thread.__init__(self) @@ -80,6 +81,7 @@ class CharacterImportThread(threading.Thread): wx.CallAfter(self.callback) + class SkillBackupThread(threading.Thread): def __init__(self, path, saveFmt, activeFit, callback): threading.Thread.__init__(self) @@ -100,11 +102,12 @@ class SkillBackupThread(threading.Thread): with gzip.open(path, mode='wb') as backupFile: backupFile.write(backupData) else: - with open(path, mode='w',encoding='utf-8') as backupFile: + with open(path, mode='w', encoding='utf-8') as backupFile: backupFile.write(backupData) wx.CallAfter(self.callback) + class Character(object): instance = None skillReqsDict = {} @@ -151,7 +154,7 @@ class Character(object): 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 + pass # Duplicate skills confuse EVEMon else: skillsSeen.add(skillKey) entry = ElementTree.SubElement(root, "entry") @@ -230,7 +233,7 @@ class Character(object): group = eos.db.getGroup(groupID) skills = [] for skill in group.items: - if skill.published == True: + if skill.published is True: skills.append((skill.ID, skill.name)) return skills @@ -305,7 +308,7 @@ class Character(object): if char.name == charName: charID = char.characterID - if charID == None: + if charID is None: return sheet = auth.character(charID).CharacterSheet() diff --git a/service/crest.py b/service/crest.py index edb6af5c5..4af0ccb89 100644 --- a/service/crest.py +++ b/service/crest.py @@ -16,16 +16,18 @@ from service.pycrest.eve import EVE logger = logging.getLogger(__name__) + class Servers(Enum): TQ = 0 SISI = 1 + class CrestModes(Enum): IMPLICIT = 0 USER = 1 -class Crest(): +class Crest(): clientIDs = { Servers.TQ: 'f9be379951c046339dc13a00e6be7704', Servers.SISI: 'af87365240d644f7950af563b8418bad' @@ -36,9 +38,10 @@ class Crest(): clientTest = True _instance = None + @classmethod def getInstance(cls): - if cls._instance == None: + if cls._instance is None: cls._instance = Crest() return cls._instance @@ -74,7 +77,8 @@ class Crest(): # Base EVE connection that is copied to all characters self.eve = EVE( - client_id=self.settings.get('clientID') if self.settings.get('mode') == CrestModes.USER else self.clientIDs.get(self.settings.get('server')), + client_id=self.settings.get('clientID') if self.settings.get( + 'mode') == CrestModes.USER else self.clientIDs.get(self.settings.get('server')), api_key=self.settings.get('clientSecret') if self.settings.get('mode') == CrestModes.USER else None, redirect_uri=self.clientCallback, testing=self.isTestServer @@ -134,16 +138,16 @@ class Crest(): def getFittings(self, charID): char = self.getCrestCharacter(charID) - return char.eve.get('%scharacters/%d/fittings/'%(char.eve._authed_endpoint,char.ID)) + return char.eve.get('%scharacters/%d/fittings/' % (char.eve._authed_endpoint, char.ID)) def postFitting(self, charID, json): - #@todo: new fitting ID can be recovered from Location header, ie: Location -> https://api-sisi.testeveonline.com/characters/1611853631/fittings/37486494/ + # @todo: new fitting ID can be recovered from Location header, ie: Location -> https://api-sisi.testeveonline.com/characters/1611853631/fittings/37486494/ char = self.getCrestCharacter(charID) - return char.eve.post('%scharacters/%d/fittings/'%(char.eve._authed_endpoint,char.ID), data=json) + return char.eve.post('%scharacters/%d/fittings/' % (char.eve._authed_endpoint, char.ID), data=json) def delFitting(self, charID, fittingID): char = self.getCrestCharacter(charID) - return char.eve.delete('%scharacters/%d/fittings/%d/'%(char.eve._authed_endpoint, char.ID, fittingID)) + return char.eve.delete('%scharacters/%d/fittings/%d/' % (char.eve._authed_endpoint, char.ID, fittingID)) def logout(self): """Logout of implicit character""" @@ -160,7 +164,8 @@ class Crest(): logging.debug("Starting server") if self.httpd: self.stopServer() - time.sleep(1) # we need this to ensure that the previous get_request finishes, and then the socket will close + time.sleep(1) + # we need this to ensure that the previous get_request finishes, and then the socket will close self.httpd = StoppableHTTPServer(('', 6461), AuthHandler) thread.start_new_thread(self.httpd.serve, (self.handleLogin,)) @@ -175,7 +180,7 @@ class Crest(): logger.warn("OAUTH state mismatch") return - logger.debug("Handling CREST login with: %s"%message) + logger.debug("Handling CREST login with: %s" % message) if 'access_token' in message: # implicit eve = copy.deepcopy(self.eve) @@ -193,7 +198,7 @@ class Crest(): self.implicitCharacter = CrestChar(info['CharacterID'], info['CharacterName']) self.implicitCharacter.eve = eve - #self.implicitCharacter.fetchImage() + # self.implicitCharacter.fetchImage() wx.PostEvent(self.mainFrame, GE.SsoLogin(type=CrestModes.IMPLICIT)) elif 'code' in message: diff --git a/service/damagePattern.py b/service/damagePattern.py index ac5fc8b44..2343693d3 100644 --- a/service/damagePattern.py +++ b/service/damagePattern.py @@ -22,12 +22,14 @@ import copy import eos.db from eos.saveddata.damagePattern import DamagePattern as es_DamagePattern + class ImportError(Exception): - pass + pass class DamagePattern(): instance = None + @classmethod def getInstance(cls): if cls.instance is None: diff --git a/service/eveapi.py b/service/eveapi.py index 0b02f2159..d1c9835ae 100644 --- a/service/eveapi.py +++ b/service/eveapi.py @@ -174,6 +174,7 @@ proxySSL = False _default_useragent = "eveapi.py/1.3" _useragent = None # use set_user_agent() to set this. + # ----------------------------------------------------------------------------- @@ -309,6 +310,7 @@ def _ParseXML(response, fromContext, storeFunc): return result + # ----------------------------------------------------------------------------- # API Classes # ----------------------------------------------------------------------------- @@ -414,7 +416,8 @@ class _RootContext(_Context): if retrieve_fallback: # implementor is handling fallbacks... try: - return _ParseXML(response, True, store and (lambda obj: cache.store(self._host, path, kw, response, obj))) + return _ParseXML(response, True, + store and (lambda obj: cache.store(self._host, path, kw, response, obj))) except Error as e: response = retrieve_fallback(self._host, path, kw, reason=e) if response is not None: @@ -563,7 +566,9 @@ class _Parser(object): if not self.container._cols or (numAttr > numCols): # the row data contains more attributes than were defined. self.container._cols = attributes[0::2] - self.container.append([_castfunc(attributes[i], attributes[i + 1]) for i in xrange(0, len(attributes), 2)]) + self.container.append( + [_castfunc(attributes[i], attributes[i + 1]) for i in xrange(0, len(attributes), 2)] + ) # this._isrow = True @@ -675,9 +680,11 @@ class _Parser(object): # into a Rowset, adding the sibling element and this one. rs = Rowset() rs.__catch = rs._name = this._name - row = [_castfunc(attributes[i], attributes[i + 1]) for i in xrange(0, len(attributes), 2)] + [getattr(this, col) for col in attributes2] + row = [_castfunc(attributes[i], attributes[i + 1]) for i in xrange(0, len(attributes), 2)] + \ + [getattr(this, col) for col in attributes2] rs.append(row) - row = [getattr(sibling, attributes[i]) for i in xrange(0, len(attributes), 2)] + [getattr(sibling, col) for col in attributes2] + row = [getattr(sibling, attributes[i]) for i in xrange(0, len(attributes), 2)] + \ + [getattr(sibling, col) for col in attributes2] rs.append(row) rs._cols = [attributes[i] for i in xrange(0, len(attributes), 2)] + [col for col in attributes2] setattr(self.container, this._name, rs) diff --git a/service/fit.py b/service/fit.py index 877ce4d94..61b8ccc3b 100644 --- a/service/fit.py +++ b/service/fit.py @@ -17,39 +17,30 @@ # along with pyfa. If not, see . # =============================================================================== -import locale import copy -import threading import logging -import wx -from codecs import open - -import xml.parsers.expat import eos.db -from eos.types import State, Slot, Module, Drone, Fighter, Fit as FitType - -from eos.saveddata.ship import Ship as es_Ship -from eos.saveddata.citadel import Citadel as es_Citadel -from eos.saveddata.implant import Implant as es_Implant from eos.saveddata.booster import Booster as es_Booster -from eos.saveddata.module import Module as es_Module -from eos.saveddata.fighter import Fighter as es_Fighter -from eos.saveddata.drone import Drone as es_Drone from eos.saveddata.cargo import Cargo as es_Cargo -from eos.saveddata.damagePattern import DamagePattern as es_DamagePattern - -from service.market import Market -from service.damagePattern import DamagePattern -from service.character import Character from eos.saveddata.character import Character as saveddata_Character +from eos.saveddata.citadel import Citadel as es_Citadel +from eos.saveddata.damagePattern import DamagePattern as es_DamagePattern +from eos.saveddata.drone import Drone as es_Drone +from eos.saveddata.fighter import Fighter as es_Fighter +from eos.saveddata.implant import Implant as es_Implant +from eos.saveddata.module import Module as es_Module +from eos.saveddata.ship import Ship as es_Ship +from eos.types import State, Slot, Fit as FitType +from service.character import Character +from service.damagePattern import DamagePattern from service.fleet import Fleet +from service.market import Market from service.settings import SettingsProvider logger = logging.getLogger(__name__) - class Fit(object): instance = None diff --git a/service/fleet.py b/service/fleet.py index a3f255336..fe59e8af8 100644 --- a/service/fleet.py +++ b/service/fleet.py @@ -1,217 +1,218 @@ -# ============================================================================= -# 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 copy -import eos.db -from eos.saveddata.fleet import Fleet as Fleet_ -from eos.saveddata.fleet import Fleet as Wing -from eos.saveddata.fleet import Fleet as Squad - -class Fleet(object): - instance = None - @classmethod - def getInstance(cls): - if cls.instance is None: - cls.instance = Fleet() - - return cls.instance - - def __init__(self): - pass - - def getFleetList(self): - fleetList = [] - fleets = eos.db.getFleetList() - for fleet in fleets: - fleetList.append((fleet.ID, fleet.name, fleet.count())) - - return fleetList - - def getFleetByID(self, ID): - f = eos.db.getFleet(ID) - return f - - def addFleet(self): - f = Fleet_() - eos.db.save(f) - return f - - def renameFleet(self, fleet, newName): - fleet.name = newName - eos.db.commit() - - def copyFleet(self, fleet): - newFleet = copy.deepcopy(fleet) - eos.db.save(newFleet) - return newFleet - - def copyFleetByID(self, ID): - fleet = self.getFleetByID(ID) - return self.copyFleet(fleet) - - def deleteFleet(self, fleet): - eos.db.remove(fleet) - - def deleteFleetByID(self, ID): - fleet = self.getFleetByID(ID) - self.deleteFleet(fleet) - - def makeLinearFleet(self, fit): - f = Fleet_() - w = Wing() - f.wings.append(w) - s = Squad() - w.squads.append(s) - s.members.append(fit) - fit.fleet = f - eos.db.save(f) - - def setLinearFleetCom(self, boostee, booster): - #if boostee == booster: - # return - if self.getLinearFleet(boostee) is None: - self.removeAssociatedFleetData(boostee) - self.makeLinearFleet(boostee) - squadIDs = set(eos.db.getSquadsIDsWithFitID(boostee.ID)) - squad = eos.db.getSquad(squadIDs.pop()) - if squad.wing.gang.leader is not None and booster is None: - try: - squad.wing.gang.leader.boostsFits.remove(boostee.ID) - except KeyError: - pass - squad.wing.gang.leader = booster - if self.anyBoosters(squad) is False: - self.removeAssociatedFleetData(boostee) - from service.fit import Fit - sFit = Fit.getInstance() - sFit.recalc(boostee, withBoosters=True) - - def setLinearWingCom(self, boostee, booster): - #if boostee == booster: - # return - if self.getLinearFleet(boostee) is None: - self.removeAssociatedFleetData(boostee) - self.makeLinearFleet(boostee) - squadIDs = set(eos.db.getSquadsIDsWithFitID(boostee.ID)) - squad = eos.db.getSquad(squadIDs.pop()) - if squad.wing.leader is not None and booster is None: - try: - squad.wing.leader.boostsFits.remove(boostee.ID) - except KeyError: - pass - squad.wing.leader = booster - if self.anyBoosters(squad) is False: - self.removeAssociatedFleetData(boostee) - from service.fit import Fit - sFit = Fit.getInstance() - sFit.recalc(boostee, withBoosters=True) - - def setLinearSquadCom(self, boostee, booster): - #if boostee == booster: - # return - if self.getLinearFleet(boostee) is None: - self.removeAssociatedFleetData(boostee) - self.makeLinearFleet(boostee) - squadIDs = set(eos.db.getSquadsIDsWithFitID(boostee.ID)) - squad = eos.db.getSquad(squadIDs.pop()) - if squad.leader is not None and booster is None: - try: - squad.leader.boostsFits.remove(boostee.ID) - except KeyError: - pass - squad.leader = booster - if self.anyBoosters(squad) is False: - self.removeAssociatedFleetData(boostee) - from service.fit import Fit - sFit = Fit.getInstance() - sFit.recalc(boostee, withBoosters=True) - - - def getLinearFleet(self, fit): - sqIDs = eos.db.getSquadsIDsWithFitID(fit.ID) - if len(sqIDs) != 1: - return None - s = eos.db.getSquad(sqIDs[0]) - if len(s.members) != 1: - return None - w = s.wing - if len(w.squads) != 1: - return None - f = w.gang - if len(f.wings) != 1: - return None - return f - - def removeAssociatedFleetData(self, fit): - squadIDs = set(eos.db.getSquadsIDsWithFitID(fit.ID)) - if len(squadIDs) == 0: - return - squads = list(eos.db.getSquad(sqID) for sqID in squadIDs) - wingIDs = set(squad.wing.ID for squad in squads) - fleetIDs = set(squad.wing.gang.ID for squad in squads) - for fleetID in fleetIDs: - fleet = eos.db.getFleet(fleetID) - for wing in fleet.wings: - wingIDs.add(wing.ID) - for wingID in wingIDs: - wing = eos.db.getWing(wingID) - for squad in wing.squads: - squadIDs.add(squad.ID) - for squadID in squadIDs: - squad = eos.db.getSquad(squadID) - if squad.leader is not None: - try: - squad.leader.boostsFits.remove(fit.ID) - except KeyError: - pass - eos.db.remove(squad) - for wingID in wingIDs: - wing = eos.db.getWing(wingID) - if wing.leader is not None: - try: - wing.leader.boostsFits.remove(fit.ID) - except KeyError: - pass - eos.db.remove(wing) - for fleetID in fleetIDs: - fleet = eos.db.getFleet(fleetID) - if fleet.leader is not None: - try: - fleet.leader.boostsFits.remove(fit.ID) - except KeyError: - pass - eos.db.remove(fleet) - fit.fleet = None - return - - def anyBoosters(self, squad): - wing = squad.wing - fleet = wing.gang - if squad.leader is None and wing.leader is None and fleet.leader is None: - return False - return True - - def loadLinearFleet(self, fit): - if self.getLinearFleet(fit) is None: - return None - squadID = eos.db.getSquadsIDsWithFitID(fit.ID)[0] - s = eos.db.getSquad(squadID) - w = s.wing - f = w.gang - return (f.leader, w.leader, s.leader) +# ============================================================================= +# 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 copy +import eos.db +from eos.saveddata.fleet import Fleet as Fleet_ +from eos.saveddata.fleet import Fleet as Wing +from eos.saveddata.fleet import Fleet as Squad + + +class Fleet(object): + instance = None + + @classmethod + def getInstance(cls): + if cls.instance is None: + cls.instance = Fleet() + + return cls.instance + + def __init__(self): + pass + + def getFleetList(self): + fleetList = [] + fleets = eos.db.getFleetList() + for fleet in fleets: + fleetList.append((fleet.ID, fleet.name, fleet.count())) + + return fleetList + + def getFleetByID(self, ID): + f = eos.db.getFleet(ID) + return f + + def addFleet(self): + f = Fleet_() + eos.db.save(f) + return f + + def renameFleet(self, fleet, newName): + fleet.name = newName + eos.db.commit() + + def copyFleet(self, fleet): + newFleet = copy.deepcopy(fleet) + eos.db.save(newFleet) + return newFleet + + def copyFleetByID(self, ID): + fleet = self.getFleetByID(ID) + return self.copyFleet(fleet) + + def deleteFleet(self, fleet): + eos.db.remove(fleet) + + def deleteFleetByID(self, ID): + fleet = self.getFleetByID(ID) + self.deleteFleet(fleet) + + def makeLinearFleet(self, fit): + f = Fleet_() + w = Wing() + f.wings.append(w) + s = Squad() + w.squads.append(s) + s.members.append(fit) + fit.fleet = f + eos.db.save(f) + + def setLinearFleetCom(self, boostee, booster): + # if boostee == booster: + # return + if self.getLinearFleet(boostee) is None: + self.removeAssociatedFleetData(boostee) + self.makeLinearFleet(boostee) + squadIDs = set(eos.db.getSquadsIDsWithFitID(boostee.ID)) + squad = eos.db.getSquad(squadIDs.pop()) + if squad.wing.gang.leader is not None and booster is None: + try: + squad.wing.gang.leader.boostsFits.remove(boostee.ID) + except KeyError: + pass + squad.wing.gang.leader = booster + if self.anyBoosters(squad) is False: + self.removeAssociatedFleetData(boostee) + from service.fit import Fit + sFit = Fit.getInstance() + sFit.recalc(boostee, withBoosters=True) + + def setLinearWingCom(self, boostee, booster): + # if boostee == booster: + # return + if self.getLinearFleet(boostee) is None: + self.removeAssociatedFleetData(boostee) + self.makeLinearFleet(boostee) + squadIDs = set(eos.db.getSquadsIDsWithFitID(boostee.ID)) + squad = eos.db.getSquad(squadIDs.pop()) + if squad.wing.leader is not None and booster is None: + try: + squad.wing.leader.boostsFits.remove(boostee.ID) + except KeyError: + pass + squad.wing.leader = booster + if self.anyBoosters(squad) is False: + self.removeAssociatedFleetData(boostee) + from service.fit import Fit + sFit = Fit.getInstance() + sFit.recalc(boostee, withBoosters=True) + + def setLinearSquadCom(self, boostee, booster): + # if boostee == booster: + # return + if self.getLinearFleet(boostee) is None: + self.removeAssociatedFleetData(boostee) + self.makeLinearFleet(boostee) + squadIDs = set(eos.db.getSquadsIDsWithFitID(boostee.ID)) + squad = eos.db.getSquad(squadIDs.pop()) + if squad.leader is not None and booster is None: + try: + squad.leader.boostsFits.remove(boostee.ID) + except KeyError: + pass + squad.leader = booster + if self.anyBoosters(squad) is False: + self.removeAssociatedFleetData(boostee) + from service.fit import Fit + sFit = Fit.getInstance() + sFit.recalc(boostee, withBoosters=True) + + def getLinearFleet(self, fit): + sqIDs = eos.db.getSquadsIDsWithFitID(fit.ID) + if len(sqIDs) != 1: + return None + s = eos.db.getSquad(sqIDs[0]) + if len(s.members) != 1: + return None + w = s.wing + if len(w.squads) != 1: + return None + f = w.gang + if len(f.wings) != 1: + return None + return f + + def removeAssociatedFleetData(self, fit): + squadIDs = set(eos.db.getSquadsIDsWithFitID(fit.ID)) + if len(squadIDs) == 0: + return + squads = list(eos.db.getSquad(sqID) for sqID in squadIDs) + wingIDs = set(squad.wing.ID for squad in squads) + fleetIDs = set(squad.wing.gang.ID for squad in squads) + for fleetID in fleetIDs: + fleet = eos.db.getFleet(fleetID) + for wing in fleet.wings: + wingIDs.add(wing.ID) + for wingID in wingIDs: + wing = eos.db.getWing(wingID) + for squad in wing.squads: + squadIDs.add(squad.ID) + for squadID in squadIDs: + squad = eos.db.getSquad(squadID) + if squad.leader is not None: + try: + squad.leader.boostsFits.remove(fit.ID) + except KeyError: + pass + eos.db.remove(squad) + for wingID in wingIDs: + wing = eos.db.getWing(wingID) + if wing.leader is not None: + try: + wing.leader.boostsFits.remove(fit.ID) + except KeyError: + pass + eos.db.remove(wing) + for fleetID in fleetIDs: + fleet = eos.db.getFleet(fleetID) + if fleet.leader is not None: + try: + fleet.leader.boostsFits.remove(fit.ID) + except KeyError: + pass + eos.db.remove(fleet) + fit.fleet = None + return + + def anyBoosters(self, squad): + wing = squad.wing + fleet = wing.gang + if squad.leader is None and wing.leader is None and fleet.leader is None: + return False + return True + + def loadLinearFleet(self, fit): + if self.getLinearFleet(fit) is None: + return None + squadID = eos.db.getSquadsIDsWithFitID(fit.ID)[0] + s = eos.db.getSquad(squadID) + w = s.wing + f = w.gang + return (f.leader, w.leader, s.leader) diff --git a/service/implantSet.py b/service/implantSet.py index add0808cc..f7df17bab 100644 --- a/service/implantSet.py +++ b/service/implantSet.py @@ -24,9 +24,11 @@ from service.market import Market from eos.saveddata.implant import Implant as es_Implant from eos.saveddata.implantSet import ImplantSet as es_ImplantSet + class ImportError(Exception): pass + class ImplantSets(object): instance = None diff --git a/service/market.py b/service/market.py index 4ea0ee5ca..30972795e 100644 --- a/service/market.py +++ b/service/market.py @@ -1,4 +1,4 @@ -#=============================================================================== +# =============================================================================== # Copyright (C) 2010 Diego Duclos # # This file is part of pyfa. @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with pyfa. If not, see . -#=============================================================================== +# =============================================================================== import re import threading @@ -43,6 +43,7 @@ logger = logging.getLogger(__name__) # Event which tells threads dependent on Market that it's initialized mktRdy = threading.Event() + class ShipBrowserWorkerThread(threading.Thread): def run(self): self.queue = Queue.Queue() @@ -73,6 +74,7 @@ class ShipBrowserWorkerThread(threading.Thread): except: pass + class PriceWorkerThread(threading.Thread): def run(self): self.queue = Queue.Queue() @@ -107,6 +109,7 @@ class PriceWorkerThread(threading.Thread): self.wait[itemID] = [] self.wait[itemID].append(callback) + class SearchWorkerThread(threading.Thread): def run(self): self.cv = threading.Condition() @@ -131,7 +134,7 @@ class SearchWorkerThread(threading.Thread): elif filterOn: # filter by selected categories filter = e_Category.name.in_(filterOn) else: - filter=None + filter = None results = eos.db.searchItems(request, where=filter, join=(e_Item.group, e_Group.category), @@ -150,15 +153,18 @@ class SearchWorkerThread(threading.Thread): self.cv.notify() self.cv.release() + class Market(): instance = None + def __init__(self): self.priceCache = {} - #Init recently used module storage + # Init recently used module storage serviceMarketRecentlyUsedModules = {"pyfaMarketRecentlyUsedModules": []} - self.serviceMarketRecentlyUsedModules = SettingsProvider.getInstance().getSettings("pyfaMarketRecentlyUsedModules", serviceMarketRecentlyUsedModules) + self.serviceMarketRecentlyUsedModules = SettingsProvider.getInstance().getSettings( + "pyfaMarketRecentlyUsedModules", serviceMarketRecentlyUsedModules) # Start price fetcher self.priceWorkerThread = PriceWorkerThread() @@ -188,36 +194,37 @@ class Market(): self.les_grp.description = "" self.les_grp.icon = None self.ITEMS_FORCEGROUP = { - "Opux Luxury Yacht": self.les_grp, # One of those is wedding present at CCP fanfest, another was hijacked from ISD guy during an event + "Opux Luxury Yacht": self.les_grp, + # One of those is wedding present at CCP fanfest, another was hijacked from ISD guy during an event "Silver Magnate": self.les_grp, # Amarr Championship prize "Gold Magnate": self.les_grp, # Amarr Championship prize "Armageddon Imperial Issue": self.les_grp, # Amarr Championship prize - "Apocalypse Imperial Issue": self.les_grp, # Amarr Championship prize - "Guardian-Vexor": self.les_grp, # Illegal rewards for the Gallente Frontier Tour Lines event arc - "Megathron Federate Issue": self.les_grp, # Reward during Crielere event + "Apocalypse Imperial Issue": self.les_grp, # Amarr Championship prize + "Guardian-Vexor": self.les_grp, # Illegal rewards for the Gallente Frontier Tour Lines event arc + "Megathron Federate Issue": self.les_grp, # Reward during Crielere event "Raven State Issue": self.les_grp, # AT4 prize - "Tempest Tribal Issue": self.les_grp, # AT4 prize - "Apotheosis": self.les_grp, # 5th EVE anniversary present - "Zephyr": self.les_grp, # 2010 new year gift - "Primae": self.les_grp, # Promotion of planetary interaction - "Freki": self.les_grp, # AT7 prize - "Mimir": self.les_grp, # AT7 prize - "Utu": self.les_grp, # AT8 prize - "Adrestia": self.les_grp, # AT8 prize - "Echelon": self.les_grp, # 2011 new year gift - "Malice": self.les_grp, # AT9 prize - "Vangel": self.les_grp, # AT9 prize - "Cambion": self.les_grp, # AT10 prize - "Etana": self.les_grp, # AT10 prize - "Chremoas": self.les_grp, # AT11 prize :( - "Moracha": self.les_grp, # AT11 prize - "Stratios Emergency Responder": self.les_grp, # Issued for Somer Blink lottery - "Miasmos Quafe Ultra Edition": self.les_grp, # Gift to people who purchased FF HD stream + "Tempest Tribal Issue": self.les_grp, # AT4 prize + "Apotheosis": self.les_grp, # 5th EVE anniversary present + "Zephyr": self.les_grp, # 2010 new year gift + "Primae": self.les_grp, # Promotion of planetary interaction + "Freki": self.les_grp, # AT7 prize + "Mimir": self.les_grp, # AT7 prize + "Utu": self.les_grp, # AT8 prize + "Adrestia": self.les_grp, # AT8 prize + "Echelon": self.les_grp, # 2011 new year gift + "Malice": self.les_grp, # AT9 prize + "Vangel": self.les_grp, # AT9 prize + "Cambion": self.les_grp, # AT10 prize + "Etana": self.les_grp, # AT10 prize + "Chremoas": self.les_grp, # AT11 prize :( + "Moracha": self.les_grp, # AT11 prize + "Stratios Emergency Responder": self.les_grp, # Issued for Somer Blink lottery + "Miasmos Quafe Ultra Edition": self.les_grp, # Gift to people who purchased FF HD stream "InterBus Shuttle": self.les_grp, - "Leopard": self.les_grp, # 2013 new year gift - "Whiptail": self.les_grp, # AT12 prize - "Chameleon": self.les_grp, # AT12 prize - "Victorieux Luxury Yacht": self.les_grp, # Worlds Collide prize \o/ chinese getting owned + "Leopard": self.les_grp, # 2013 new year gift + "Whiptail": self.les_grp, # AT12 prize + "Chameleon": self.les_grp, # AT12 prize + "Victorieux Luxury Yacht": self.les_grp, # Worlds Collide prize \o/ chinese getting owned "Imp": self.les_grp, # AT13 prize "Fiend": self.les_grp, # AT13 prize } @@ -228,8 +235,8 @@ class Market(): # List of items which are forcibly published or hidden self.ITEMS_FORCEPUBLISHED = { - "Data Subverter I": False, # Not used in EVE, probably will appear with Dust link - "QA Cross Protocol Analyzer": False, # QA modules used by CCP internally + "Data Subverter I": False, # Not used in EVE, probably will appear with Dust link + "QA Cross Protocol Analyzer": False, # QA modules used by CCP internally "QA Damage Module": False, "QA ECCM": False, "QA Immunity Module": False, @@ -265,7 +272,7 @@ class Market(): # List of groups which are forcibly published self.GROUPS_FORCEPUBLISHED = { - "Prototype Exploration Ship": False } # We moved the only ship from this group to other group anyway + "Prototype Exploration Ship": False} # We moved the only ship from this group to other group anyway # Dictionary of items with forced meta groups, uses following format: # Item name: (metagroup name, parent type name) @@ -274,75 +281,96 @@ class Market(): "'Wild' Miner I": ("Storyline", "Miner I"), "Medium Nano Armor Repair Unit I": ("Tech I", "Medium Armor Repairer I"), "Large 'Reprieve' Vestment Reconstructer I": ("Storyline", "Large Armor Repairer I"), - "Khanid Navy Torpedo Launcher": ("Faction", "Torpedo Launcher I"),} + "Khanid Navy Torpedo Launcher": ("Faction", "Torpedo Launcher I"), } # Parent type name: set(item names) self.ITEMS_FORCEDMETAGROUP_R = {} for item, value in self.ITEMS_FORCEDMETAGROUP.items(): parent = value[1] - if not parent in self.ITEMS_FORCEDMETAGROUP_R: + if parent not in self.ITEMS_FORCEDMETAGROUP_R: self.ITEMS_FORCEDMETAGROUP_R[parent] = set() self.ITEMS_FORCEDMETAGROUP_R[parent].add(item) # Dictionary of items with forced market group (service assumes they have no # market group assigned in db, otherwise they'll appear in both original and forced groups) self.ITEMS_FORCEDMARKETGROUP = { - "'Alpha' Data Analyzer I": 714, # Ship Equipment > Electronics and Sensor Upgrades > Scanners > Data and Composition Scanners - "'Codex' Data Analyzer I": 714, # Ship Equipment > Electronics and Sensor Upgrades > Scanners > Data and Composition Scanners - "'Daemon' Data Analyzer I": 714, # Ship Equipment > Electronics and Sensor Upgrades > Scanners > Data and Composition Scanners - "'Libram' Data Analyzer I": 714, # Ship Equipment > Electronics and Sensor Upgrades > Scanners > Data and Composition Scanners - "Advanced Cerebral Accelerator": 977, # Implants & Boosters > Booster - "Civilian Damage Control": 615, # Ship Equipment > Hull & Armor > Damage Controls - "Civilian EM Ward Field": 1695, # Ship Equipment > Shield > Shield Hardeners > EM Shield Hardeners - "Civilian Explosive Deflection Field": 1694, # Ship Equipment > Shield > Shield Hardeners > Explosive Shield Hardeners - "Civilian Hobgoblin": 837, # Drones > Combat Drones > Light Scout Drones - "Civilian Kinetic Deflection Field": 1693, # Ship Equipment > Shield > Shield Hardeners > Kinetic Shield Hardeners - "Civilian Light Missile Launcher": 640, # Ship Equipment > Turrets & Bays > Missile Launchers > Light Missile Launchers - "Civilian Scourge Light Missile": 920, # Ammunition & Charges > Missiles > Light Missiles > Standard Light Missiles - "Civilian Small Remote Armor Repairer": 1059, # Ship Equipment > Hull & Armor > Remote Armor Repairers > Small - "Civilian Small Remote Shield Booster": 603, # Ship Equipment > Shield > Remote Shield Boosters > Small - "Civilian Stasis Webifier": 683, # Ship Equipment > Electronic Warfare > Stasis Webifiers - "Civilian Thermic Dissipation Field": 1692, # Ship Equipment > Shield > Shield Hardeners > Thermal Shield Hardeners - "Civilian Warp Disruptor": 1935, # Ship Equipment > Electronic Warfare > Warp Disruptors - "Hardwiring - Zainou 'Sharpshooter' ZMX10": 1493, # Implants & Boosters > Implants > Skill Hardwiring > Missile Implants > Implant Slot 06 - "Hardwiring - Zainou 'Sharpshooter' ZMX100": 1493, # Implants & Boosters > Implants > Skill Hardwiring > Missile Implants > Implant Slot 06 - "Hardwiring - Zainou 'Sharpshooter' ZMX1000": 1493, # Implants & Boosters > Implants > Skill Hardwiring > Missile Implants > Implant Slot 06 - "Hardwiring - Zainou 'Sharpshooter' ZMX11": 1493, # Implants & Boosters > Implants > Skill Hardwiring > Missile Implants > Implant Slot 06 - "Hardwiring - Zainou 'Sharpshooter' ZMX110": 1493, # Implants & Boosters > Implants > Skill Hardwiring > Missile Implants > Implant Slot 06 - "Hardwiring - Zainou 'Sharpshooter' ZMX1100": 1493, # Implants & Boosters > Implants > Skill Hardwiring > Missile Implants > Implant Slot 06 - "Nugoehuvi Synth Blue Pill Booster": 977, # Implants & Boosters > Booster - "Prototype Cerebral Accelerator": 977, # Implants & Boosters > Booster - "Prototype Iris Probe Launcher": 712, # Ship Equipment > Turrets & Bays > Scan Probe Launchers - "Shadow": 1310, # Drones > Combat Drones > Fighter Bombers - "Sleeper Data Analyzer I": 714, # Ship Equipment > Electronics and Sensor Upgrades > Scanners > Data and Composition Scanners - "Standard Cerebral Accelerator": 977, # Implants & Boosters > Booster - "Talocan Data Analyzer I": 714, # Ship Equipment > Electronics and Sensor Upgrades > Scanners > Data and Composition Scanners - "Terran Data Analyzer I": 714, # Ship Equipment > Electronics and Sensor Upgrades > Scanners > Data and Composition Scanners - "Tetrimon Data Analyzer I": 714 # Ship Equipment > Electronics and Sensor Upgrades > Scanners > Data and Composition Scanners + "'Alpha' Data Analyzer I": 714, + # Ship Equipment > Electronics and Sensor Upgrades > Scanners > Data and Composition Scanners + "'Codex' Data Analyzer I": 714, + # Ship Equipment > Electronics and Sensor Upgrades > Scanners > Data and Composition Scanners + "'Daemon' Data Analyzer I": 714, + # Ship Equipment > Electronics and Sensor Upgrades > Scanners > Data and Composition Scanners + "'Libram' Data Analyzer I": 714, + # Ship Equipment > Electronics and Sensor Upgrades > Scanners > Data and Composition Scanners + "Advanced Cerebral Accelerator": 977, # Implants & Boosters > Booster + "Civilian Damage Control": 615, # Ship Equipment > Hull & Armor > Damage Controls + "Civilian EM Ward Field": 1695, # Ship Equipment > Shield > Shield Hardeners > EM Shield Hardeners + "Civilian Explosive Deflection Field": 1694, + # Ship Equipment > Shield > Shield Hardeners > Explosive Shield Hardeners + "Civilian Hobgoblin": 837, # Drones > Combat Drones > Light Scout Drones + "Civilian Kinetic Deflection Field": 1693, + # Ship Equipment > Shield > Shield Hardeners > Kinetic Shield Hardeners + "Civilian Light Missile Launcher": 640, + # Ship Equipment > Turrets & Bays > Missile Launchers > Light Missile Launchers + "Civilian Scourge Light Missile": 920, + # Ammunition & Charges > Missiles > Light Missiles > Standard Light Missiles + "Civilian Small Remote Armor Repairer": 1059, + # Ship Equipment > Hull & Armor > Remote Armor Repairers > Small + "Civilian Small Remote Shield Booster": 603, # Ship Equipment > Shield > Remote Shield Boosters > Small + "Civilian Stasis Webifier": 683, # Ship Equipment > Electronic Warfare > Stasis Webifiers + "Civilian Thermic Dissipation Field": 1692, + # Ship Equipment > Shield > Shield Hardeners > Thermal Shield Hardeners + "Civilian Warp Disruptor": 1935, # Ship Equipment > Electronic Warfare > Warp Disruptors + "Hardwiring - Zainou 'Sharpshooter' ZMX10": 1493, + # Implants & Boosters > Implants > Skill Hardwiring > Missile Implants > Implant Slot 06 + "Hardwiring - Zainou 'Sharpshooter' ZMX100": 1493, + # Implants & Boosters > Implants > Skill Hardwiring > Missile Implants > Implant Slot 06 + "Hardwiring - Zainou 'Sharpshooter' ZMX1000": 1493, + # Implants & Boosters > Implants > Skill Hardwiring > Missile Implants > Implant Slot 06 + "Hardwiring - Zainou 'Sharpshooter' ZMX11": 1493, + # Implants & Boosters > Implants > Skill Hardwiring > Missile Implants > Implant Slot 06 + "Hardwiring - Zainou 'Sharpshooter' ZMX110": 1493, + # Implants & Boosters > Implants > Skill Hardwiring > Missile Implants > Implant Slot 06 + "Hardwiring - Zainou 'Sharpshooter' ZMX1100": 1493, + # Implants & Boosters > Implants > Skill Hardwiring > Missile Implants > Implant Slot 06 + "Nugoehuvi Synth Blue Pill Booster": 977, # Implants & Boosters > Booster + "Prototype Cerebral Accelerator": 977, # Implants & Boosters > Booster + "Prototype Iris Probe Launcher": 712, # Ship Equipment > Turrets & Bays > Scan Probe Launchers + "Shadow": 1310, # Drones > Combat Drones > Fighter Bombers + "Sleeper Data Analyzer I": 714, + # Ship Equipment > Electronics and Sensor Upgrades > Scanners > Data and Composition Scanners + "Standard Cerebral Accelerator": 977, # Implants & Boosters > Booster + "Talocan Data Analyzer I": 714, + # Ship Equipment > Electronics and Sensor Upgrades > Scanners > Data and Composition Scanners + "Terran Data Analyzer I": 714, + # Ship Equipment > Electronics and Sensor Upgrades > Scanners > Data and Composition Scanners + "Tetrimon Data Analyzer I": 714 + # Ship Equipment > Electronics and Sensor Upgrades > Scanners > Data and Composition Scanners } self.ITEMS_FORCEDMARKETGROUP_R = self.__makeRevDict(self.ITEMS_FORCEDMARKETGROUP) self.FORCEDMARKETGROUP = { - 685: False, # Ship Equipment > Electronic Warfare > ECCM - 681: False, # Ship Equipment > Electronic Warfare > Sensor Backup Arrays + 685: False, # Ship Equipment > Electronic Warfare > ECCM + 681: False, # Ship Equipment > Electronic Warfare > Sensor Backup Arrays } # Misc definitions # 0 is for items w/o meta group - self.META_MAP = OrderedDict([("normal", frozenset((0, 1, 2, 14))), + self.META_MAP = OrderedDict([("normal", frozenset((0, 1, 2, 14))), ("faction", frozenset((4, 3))), ("complex", frozenset((6,))), ("officer", frozenset((5,)))]) - self.SEARCH_CATEGORIES = ("Drone", "Module", "Subsystem", "Charge", "Implant", "Deployable", "Fighter", "Structure", "Structure Module") + self.SEARCH_CATEGORIES = ( + "Drone", "Module", "Subsystem", "Charge", "Implant", "Deployable", "Fighter", "Structure", "Structure Module") self.SEARCH_GROUPS = ("Ice Product",) - self.ROOT_MARKET_GROUPS = (9, # Modules + self.ROOT_MARKET_GROUPS = (9, # Modules 1111, # Rigs - 157, # Drones - 11, # Ammo + 157, # Drones + 11, # Ammo 1112, # Subsystems - 24, # Implants & Boosters - 404, # Deployables + 24, # Implants & Boosters + 404, # Deployables 2202, # Structure Equipment - 2203 # Structure Modifications + 2203 # Structure Modifications ) # Tell other threads that Market is at their service mktRdy.set() @@ -560,7 +588,8 @@ class Market(): groupItems = set(group.items) if hasattr(group, 'addItems'): groupItems.update(group.addItems) - items = set(filter(lambda item: self.getPublicityByItem(item) and self.getGroupByItem(item) == group, groupItems)) + items = set( + filter(lambda item: self.getPublicityByItem(item) and self.getGroupByItem(item) == group, groupItems)) return items def getItemsByMarketGroup(self, mg, vars=True): diff --git a/service/network.py b/service/network.py index 48769a48b..481c77a41 100644 --- a/service/network.py +++ b/service/network.py @@ -29,21 +29,27 @@ from service.settings import NetworkSettings timeout = 3 socket.setdefaulttimeout(timeout) + class Error(StandardError): def __init__(self, msg=None): self.message = msg + class RequestError(StandardError): pass + class AuthenticationError(StandardError): - pass + pass + class ServerError(StandardError): - pass + pass + class TimeoutError(StandardError): - pass + pass + class Network(): # Request constants - every request must supply this, as it is checked if @@ -73,7 +79,8 @@ class Network(): raise Error("Access not enabled - please enable in Preferences > Network") # Set up some things for the request - versionString = "{0} {1} - {2} {3}".format(config.version, config.tag, config.expansionName, config.expansionVersion) + versionString = "{0} {1} - {2} {3}".format(config.version, config.tag, config.expansionName, + config.expansionVersion) headers = {"User-Agent": "pyfa {0} (Python-urllib2)".format(versionString)} proxy = NetworkSettings.getInstance().getProxySettings() diff --git a/service/port.py b/service/port.py index 3f2751b4f..d6645ed23 100644 --- a/service/port.py +++ b/service/port.py @@ -171,6 +171,7 @@ class Port(object): return fits """Service which houses all import/export format functions""" + @classmethod def exportCrest(cls, ofit, callback=None): # A few notes: @@ -379,6 +380,7 @@ class Port(object): if len(s) > 10: return s[:10] + "..." return s + logger.exception("Couldn't import ship data %r", [logtransform(s) for s in info]) return None @@ -821,7 +823,8 @@ class Port(object): slot = module.slot if slot not in stuff: stuff[slot] = [] - curr = module.item.name if module.item else ("[Empty %s slot]" % Slot.getName(slot).capitalize() if slot is not None else "") + curr = module.item.name if module.item else ( + "[Empty %s slot]" % Slot.getName(slot).capitalize() if slot is not None else "") if module.charge and sFit.serviceFittingOptions["exportCharges"]: curr += ", %s" % module.charge.name if module.state == State.OFFLINE: @@ -1075,6 +1078,7 @@ class FitBackupThread(threading.Thread): # Send done signal to GUI wx.CallAfter(self.callback, -1) + class FitImportThread(threading.Thread): def __init__(self, paths, callback): threading.Thread.__init__(self) diff --git a/service/prefetch.py b/service/prefetch.py index c3705dd54..8d8348322 100644 --- a/service/prefetch.py +++ b/service/prefetch.py @@ -1,70 +1,72 @@ -# ============================================================================= -# 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 threading -import os - -import config -from eos import db -from eos.db import migration -from eos.db.saveddata.loadDefaultDatabaseValues import DefaultDatabaseValues -from eos.saveddata.character import Character as es_Character - -class PrefetchThread(threading.Thread): - def run(self): - # We're a daemon thread, as such, interpreter might get shut down while we do stuff - # Make sure we don't throw tracebacks to console - try: - es_Character.setSkillList(db.getItemsByCategory("Skill", eager=("effects", "attributes", "attributes.info.icon", "attributes.info.unit", "icon"))) - except: - pass - - -prefetch = PrefetchThread() -prefetch.daemon = True -prefetch.start() - -# The following code does not belong here, however until we rebuild skeletons -# to include modified pyfa.py, this is the best place to put it. See GH issue -# #176 -# @ todo: move this to pyfa.py - -# Make sure the saveddata db exists -if config.savePath and not os.path.exists(config.savePath): - os.mkdir(config.savePath) - -if config.saveDB and os.path.isfile(config.saveDB): - # If database exists, run migration after init'd database - db.saveddata_meta.create_all() - migration.update(db.saveddata_engine) - # Import default database values - # Import values that must exist otherwise Pyfa breaks - DefaultDatabaseValues.importRequiredDefaults() -elif config.saveDB: - # If database does not exist, do not worry about migration. Simply - # create and set version - db.saveddata_meta.create_all() - db.saveddata_engine.execute('PRAGMA user_version = {}'.format(migration.getAppVersion())) - # Import default database values - # 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() +# ============================================================================= +# 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 threading +import os + +import config +from eos import db +from eos.db import migration +from eos.db.saveddata.loadDefaultDatabaseValues import DefaultDatabaseValues +from eos.saveddata.character import Character as es_Character + + +class PrefetchThread(threading.Thread): + def run(self): + # We're a daemon thread, as such, interpreter might get shut down while we do stuff + # Make sure we don't throw tracebacks to console + try: + es_Character.setSkillList(db.getItemsByCategory("Skill", eager=( + "effects", "attributes", "attributes.info.icon", "attributes.info.unit", "icon"))) + except: + pass + + +prefetch = PrefetchThread() +prefetch.daemon = True +prefetch.start() + +# The following code does not belong here, however until we rebuild skeletons +# to include modified pyfa.py, this is the best place to put it. See GH issue +# #176 +# @ todo: move this to pyfa.py + +# Make sure the saveddata db exists +if config.savePath and not os.path.exists(config.savePath): + os.mkdir(config.savePath) + +if config.saveDB and os.path.isfile(config.saveDB): + # If database exists, run migration after init'd database + db.saveddata_meta.create_all() + migration.update(db.saveddata_engine) + # Import default database values + # Import values that must exist otherwise Pyfa breaks + DefaultDatabaseValues.importRequiredDefaults() +elif config.saveDB: + # If database does not exist, do not worry about migration. Simply + # create and set version + db.saveddata_meta.create_all() + db.saveddata_engine.execute('PRAGMA user_version = {}'.format(migration.getAppVersion())) + # Import default database values + # 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() diff --git a/service/pycrest/eve.py b/service/pycrest/eve.py index a46ad6c91..2322c704e 100644 --- a/service/pycrest/eve.py +++ b/service/pycrest/eve.py @@ -22,7 +22,6 @@ try: except ImportError: # pragma: no cover import cPickle as pickle - logger = logging.getLogger("pycrest.eve") cache_re = re.compile(r'max-age=([0-9]+)') diff --git a/service/pycrest/weak_ciphers.py b/service/pycrest/weak_ciphers.py index ad0adec03..e3a82ca15 100644 --- a/service/pycrest/weak_ciphers.py +++ b/service/pycrest/weak_ciphers.py @@ -67,7 +67,7 @@ class WeakCiphersHTTPSConnection(urllib3.connection.VerifiedHTTPSConnection): # warnings.warn(( 'System time is way off (before {0}). This will probably ' 'lead to SSL verification errors').format( - urllib3.connection.RECENT_DATE), + urllib3.connection.RECENT_DATE), SystemTimeWarning ) @@ -103,13 +103,11 @@ class WeakCiphersHTTPSConnection(urllib3.connection.VerifiedHTTPSConnection): # class WeakCiphersHTTPSConnectionPool( - urllib3.connectionpool.HTTPSConnectionPool): - + urllib3.connectionpool.HTTPSConnectionPool): ConnectionCls = WeakCiphersHTTPSConnection class WeakCiphersPoolManager(urllib3.poolmanager.PoolManager): - def _new_pool(self, scheme, host, port): if scheme == 'https': return WeakCiphersHTTPSConnectionPool(host, port, **(self.connection_pool_kw)) diff --git a/service/server.py b/service/server.py index 49e193cee..98bd138f1 100644 --- a/service/server.py +++ b/service/server.py @@ -77,7 +77,6 @@ class AuthHandler(BaseHTTPServer.BaseHTTPRequestHandler): # http://code.activestate.com/recipes/425210-simple-stoppable-server-using-socket-timeout/ class StoppableHTTPServer(BaseHTTPServer.HTTPServer): - def server_bind(self): BaseHTTPServer.HTTPServer.server_bind(self) self.settings = CRESTSettings.getInstance() diff --git a/service/settings.py b/service/settings.py index 579d484c2..68c1a2105 100644 --- a/service/settings.py +++ b/service/settings.py @@ -119,9 +119,9 @@ class NetworkSettings(object): _instance = None # constants for serviceNetworkDefaultSettings["mode"] parameter - PROXY_MODE_NONE = 0 # 0 - No proxy + PROXY_MODE_NONE = 0 # 0 - No proxy PROXY_MODE_AUTODETECT = 1 # 1 - Auto-detected proxy settings - PROXY_MODE_MANUAL = 2 # 2 - Manual proxy settings + PROXY_MODE_MANUAL = 2 # 2 - Manual proxy settings @classmethod def getInstance(cls): @@ -254,8 +254,15 @@ class HTMLExportSettings(object): return cls._instance def __init__(self): - serviceHTMLExportDefaultSettings = {"enabled": False, "path": config.pyfaPath + os.sep + 'pyfaFits.html', "minimal": False} - self.serviceHTMLExportSettings = SettingsProvider.getInstance().getSettings("pyfaServiceHTMLExportSettings", serviceHTMLExportDefaultSettings) + serviceHTMLExportDefaultSettings = { + "enabled": False, + "path": config.pyfaPath + os.sep + 'pyfaFits.html', + "minimal": False + } + self.serviceHTMLExportSettings = SettingsProvider.getInstance().getSettings( + "pyfaServiceHTMLExportSettings", + serviceHTMLExportDefaultSettings + ) def getEnabled(self): return self.serviceHTMLExportSettings["enabled"] @@ -295,7 +302,10 @@ class UpdateSettings(object): # prerelease - If True, suppress prerelease notifications # version - Set to release tag that user does not want notifications for serviceUpdateDefaultSettings = {"prerelease": True, 'version': None} - self.serviceUpdateSettings = SettingsProvider.getInstance().getSettings("pyfaServiceUpdateSettings", serviceUpdateDefaultSettings) + self.serviceUpdateSettings = SettingsProvider.getInstance().getSettings( + "pyfaServiceUpdateSettings", + serviceUpdateDefaultSettings + ) def get(self, type): return self.serviceUpdateSettings[type] @@ -315,13 +325,15 @@ class CRESTSettings(object): return cls._instance def __init__(self): - # mode # 0 - Implicit authentication # 1 - User-supplied client details serviceCRESTDefaultSettings = {"mode": 0, "server": 0, "clientID": "", "clientSecret": "", "timeout": 60} - self.serviceCRESTSettings = SettingsProvider.getInstance().getSettings("pyfaServiceCRESTSettings", serviceCRESTDefaultSettings) + self.serviceCRESTSettings = SettingsProvider.getInstance().getSettings( + "pyfaServiceCRESTSettings", + serviceCRESTDefaultSettings + ) def get(self, type): return self.serviceCRESTSettings[type] @@ -329,5 +341,4 @@ class CRESTSettings(object): def set(self, type, value): self.serviceCRESTSettings[type] = value - # @todo: migrate fit settings (from fit service) here? diff --git a/service/targetResists.py b/service/targetResists.py index dc208e78a..2a4863c5d 100644 --- a/service/targetResists.py +++ b/service/targetResists.py @@ -24,10 +24,12 @@ from eos.saveddata.targetResists import TargetResists as es_TargetResists class ImportError(Exception): - pass + pass + class TargetResists(object): instance = None + @classmethod def getInstance(cls): if cls.instance is None: diff --git a/service/update.py b/service/update.py index 2b594bbf8..05f05efd7 100644 --- a/service/update.py +++ b/service/update.py @@ -42,7 +42,10 @@ class CheckUpdateThread(threading.Thread): try: response = network.request('https://api.github.com/repos/pyfa-org/Pyfa/releases', network.UPDATE) jsonResponse = json.loads(response.read()) - jsonResponse.sort(key=lambda x: calendar.timegm(dateutil.parser.parse(x['published_at']).utctimetuple()), reverse=True) + jsonResponse.sort( + key=lambda x: calendar.timegm(dateutil.parser.parse(x['published_at']).utctimetuple()), + reverse=True + ) for release in jsonResponse: # Suppress pre releases @@ -63,7 +66,9 @@ class CheckUpdateThread(threading.Thread): else: rVersion = release['tag_name'].replace('v', '', 1) - if config.tag is 'git' and not release['prerelease'] and self.versiontuple(rVersion) >= self.versiontuple(config.version): + if config.tag is 'git' and \ + not release['prerelease'] and \ + self.versiontuple(rVersion) >= self.versiontuple(config.version): wx.CallAfter(self.callback, release) # git (dev/Singularity) -> Stable elif config.expansionName is not "Singularity": if release['prerelease']: diff --git a/tox.ini b/tox.ini index 3ce2756f3..5eae895b9 100644 --- a/tox.ini +++ b/tox.ini @@ -12,4 +12,4 @@ commands = py.test -vv --cov Pyfa tests/ [testenv:pep8] deps = flake8 -commands = flake8 --exclude=.svn,CVS,.bzr,.hg,.git,__pycache__,venv,tests,.tox,build,dist,__init__.py --ignore=E501,E731 gui_service gui eos utils config.py pyfa.py +commands = flake8 --exclude=.svn,CVS,.bzr,.hg,.git,__pycache__,venv,tests,.tox,build,dist,__init__.py --ignore=E501,E731 service gui eos utils config.py pyfa.py