diff --git a/config.py b/config.py
index 9ffbf809b..43e01afc7 100644
--- a/config.py
+++ b/config.py
@@ -19,9 +19,9 @@ debug = False
saveInRoot = False
# Version data
-version = "1.30.1"
-tag = "git"
-expansionName = "YC119.7"
+version = "1.31.0"
+tag = "Stable "
+expansionName = "YC119.8"
expansionVersion = "1.0"
evemonMinVersion = "4081"
diff --git a/eos/effects/ammospeedmultiplier.py b/eos/effects/ammospeedmultiplier.py
index 959f959bb..1bc1086a2 100644
--- a/eos/effects/ammospeedmultiplier.py
+++ b/eos/effects/ammospeedmultiplier.py
@@ -3,7 +3,6 @@
# Used by:
# Charges from group: Festival Charges (10 of 10)
# Charges from group: Interdiction Probe (2 of 2)
-# Charges from group: Survey Probe (3 of 3)
type = "passive"
diff --git a/eos/effects/boosterarmorhppenalty.py b/eos/effects/boosterarmorhppenalty.py
index e7b202a5a..d72d57144 100644
--- a/eos/effects/boosterarmorhppenalty.py
+++ b/eos/effects/boosterarmorhppenalty.py
@@ -1,7 +1,7 @@
# boosterArmorHpPenalty
#
# Used by:
-# Implants from group: Booster (12 of 55)
+# Implants from group: Booster (12 of 48)
type = "boosterSideEffect"
# User-friendly name for the side effect
diff --git a/eos/effects/boostermaxvelocitypenalty.py b/eos/effects/boostermaxvelocitypenalty.py
index af2baa170..8ef90a731 100644
--- a/eos/effects/boostermaxvelocitypenalty.py
+++ b/eos/effects/boostermaxvelocitypenalty.py
@@ -1,7 +1,7 @@
# boosterMaxVelocityPenalty
#
# Used by:
-# Implants from group: Booster (12 of 55)
+# Implants from group: Booster (12 of 48)
type = "boosterSideEffect"
# User-friendly name for the side effect
diff --git a/eos/effects/boostershieldcapacitypenalty.py b/eos/effects/boostershieldcapacitypenalty.py
index abf077c51..64401813e 100644
--- a/eos/effects/boostershieldcapacitypenalty.py
+++ b/eos/effects/boostershieldcapacitypenalty.py
@@ -1,7 +1,7 @@
# boosterShieldCapacityPenalty
#
# Used by:
-# Implants from group: Booster (12 of 55)
+# Implants from group: Booster (12 of 48)
type = "boosterSideEffect"
# User-friendly name for the side effect
diff --git a/eos/effects/covertopsandreconopscloakmoduledelaybonus.py b/eos/effects/covertopsandreconopscloakmoduledelaybonus.py
index be7408345..f3e88d3c1 100644
--- a/eos/effects/covertopsandreconopscloakmoduledelaybonus.py
+++ b/eos/effects/covertopsandreconopscloakmoduledelaybonus.py
@@ -5,8 +5,8 @@
# Ships from group: Blockade Runner (4 of 4)
# Ships from group: Covert Ops (7 of 7)
# Ships from group: Expedition Frigate (2 of 2)
-# Ships from group: Force Recon Ship (7 of 7)
-# Ships from group: Stealth Bomber (4 of 4)
+# Ships from group: Force Recon Ship (7 of 8)
+# Ships from group: Stealth Bomber (4 of 5)
# Ships named like: Stratios (2 of 2)
# Subsystems named like: Defensive Covert Reconfiguration (4 of 4)
# Ship: Astero
diff --git a/eos/effects/covertopscpubonus1.py b/eos/effects/covertopscpubonus1.py
index ebc69c273..8c45bb186 100644
--- a/eos/effects/covertopscpubonus1.py
+++ b/eos/effects/covertopscpubonus1.py
@@ -1,7 +1,7 @@
# covertOpsCpuBonus1
#
# Used by:
-# Ships from group: Stealth Bomber (4 of 4)
+# Ships from group: Stealth Bomber (4 of 5)
# Subsystems named like: Defensive Covert Reconfiguration (4 of 4)
type = "passive"
diff --git a/eos/effects/covertopsstealthbombersiegemissilelauncerpowerneedbonus.py b/eos/effects/covertopsstealthbombersiegemissilelauncerpowerneedbonus.py
index 9f65b698a..3fb9ab56c 100644
--- a/eos/effects/covertopsstealthbombersiegemissilelauncerpowerneedbonus.py
+++ b/eos/effects/covertopsstealthbombersiegemissilelauncerpowerneedbonus.py
@@ -1,7 +1,7 @@
# covertOpsStealthBomberSiegeMissileLauncerPowerNeedBonus
#
# Used by:
-# Ships from group: Stealth Bomber (4 of 4)
+# Ships from group: Stealth Bomber (4 of 5)
type = "passive"
diff --git a/eos/effects/covertopsstealthbombertargettingdelaybonus.py b/eos/effects/covertopsstealthbombertargettingdelaybonus.py
index cb1a7b18f..e5584ccfc 100644
--- a/eos/effects/covertopsstealthbombertargettingdelaybonus.py
+++ b/eos/effects/covertopsstealthbombertargettingdelaybonus.py
@@ -2,7 +2,7 @@
#
# Used by:
# Ships from group: Black Ops (4 of 4)
-# Ships from group: Stealth Bomber (4 of 4)
+# Ships from group: Stealth Bomber (4 of 5)
# Ship: Caedes
# Ship: Chremoas
# Ship: Endurance
diff --git a/eos/effects/cynosuraldurationbonus.py b/eos/effects/cynosuraldurationbonus.py
index 388e64125..e49828315 100644
--- a/eos/effects/cynosuraldurationbonus.py
+++ b/eos/effects/cynosuraldurationbonus.py
@@ -1,7 +1,7 @@
# cynosuralDurationBonus
#
# Used by:
-# Ships from group: Force Recon Ship (6 of 7)
+# Ships from group: Force Recon Ship (6 of 8)
type = "passive"
diff --git a/eos/effects/cynosuraltheoryconsumptionbonus.py b/eos/effects/cynosuraltheoryconsumptionbonus.py
index 3d1be5194..f0f43c0fb 100644
--- a/eos/effects/cynosuraltheoryconsumptionbonus.py
+++ b/eos/effects/cynosuraltheoryconsumptionbonus.py
@@ -1,7 +1,7 @@
# cynosuralTheoryConsumptionBonus
#
# Used by:
-# Ships from group: Force Recon Ship (6 of 7)
+# Ships from group: Force Recon Ship (6 of 8)
# Skill: Cynosural Field Theory
type = "passive"
diff --git a/eos/effects/doomsdayaoeecm.py b/eos/effects/doomsdayaoeecm.py
new file mode 100644
index 000000000..230eb2f16
--- /dev/null
+++ b/eos/effects/doomsdayaoeecm.py
@@ -0,0 +1,18 @@
+# doomsdayAOEECM
+#
+# Used by:
+# Module: ECM Jammer Burst Projector
+from eos.modifiedAttributeDict import ModifiedAttributeDict
+
+type = "projected", "active"
+
+
+def handler(fit, module, context, **kwargs):
+ if "projected" in context:
+ # jam formula: 1 - (1- (jammer str/ship str))^(# of jam mods with same str))
+ strModifier = 1 - module.getModifiedItemAttr("scan{0}StrengthBonus".format(fit.scanType)) / fit.scanStrength
+
+ if 'effect' in kwargs:
+ strModifier *= ModifiedAttributeDict.getResistance(fit, kwargs['effect'])
+
+ fit.ecmProjectedStr *= strModifier
diff --git a/eos/effects/missiledmgbonus.py b/eos/effects/missiledmgbonus.py
index 48a845345..3abc18387 100644
--- a/eos/effects/missiledmgbonus.py
+++ b/eos/effects/missiledmgbonus.py
@@ -1,7 +1,7 @@
# missileDMGBonus
#
# Used by:
-# Modules from group: Ballistic Control system (17 of 17)
+# Modules from group: Ballistic Control system (18 of 18)
type = "passive"
diff --git a/eos/effects/missilelauncherspeedmultiplier.py b/eos/effects/missilelauncherspeedmultiplier.py
index e9a3c1271..9e2a28559 100644
--- a/eos/effects/missilelauncherspeedmultiplier.py
+++ b/eos/effects/missilelauncherspeedmultiplier.py
@@ -1,7 +1,7 @@
# missileLauncherSpeedMultiplier
#
# Used by:
-# Modules from group: Ballistic Control system (17 of 17)
+# Modules from group: Ballistic Control system (18 of 18)
type = "passive"
diff --git a/eos/effects/overloadrofbonus.py b/eos/effects/overloadrofbonus.py
index be8e2af22..6248b687d 100644
--- a/eos/effects/overloadrofbonus.py
+++ b/eos/effects/overloadrofbonus.py
@@ -2,7 +2,7 @@
#
# Used by:
# Modules from group: Missile Launcher Torpedo (22 of 22)
-# Items from market group: Ship Equipment > Turrets & Bays (428 of 859)
+# Items from market group: Ship Equipment > Turrets & Bays (429 of 861)
# Module: Interdiction Sphere Launcher I
type = "overheat"
diff --git a/eos/effects/reconshipcloakcpubonus1.py b/eos/effects/reconshipcloakcpubonus1.py
index 4032baabc..8f63a28f2 100644
--- a/eos/effects/reconshipcloakcpubonus1.py
+++ b/eos/effects/reconshipcloakcpubonus1.py
@@ -1,7 +1,7 @@
# reconShipCloakCpuBonus1
#
# Used by:
-# Ships from group: Force Recon Ship (6 of 7)
+# Ships from group: Force Recon Ship (6 of 8)
type = "passive"
runTime = "early"
diff --git a/eos/effects/usemissiles.py b/eos/effects/usemissiles.py
index 1971dbe46..d77c56e3e 100644
--- a/eos/effects/usemissiles.py
+++ b/eos/effects/usemissiles.py
@@ -3,7 +3,7 @@
# Used by:
# Modules from group: Missile Launcher Heavy (12 of 12)
# Modules from group: Missile Launcher Rocket (15 of 15)
-# Modules named like: Launcher (153 of 153)
+# Modules named like: Launcher (154 of 154)
type = 'active', "projected"
diff --git a/eos/saveddata/character.py b/eos/saveddata/character.py
index 0105add30..d68e43114 100644
--- a/eos/saveddata/character.py
+++ b/eos/saveddata/character.py
@@ -346,7 +346,7 @@ class Skill(HandledItem):
return self.activeLevel or 0
- def setLevel(self, level, persist=False):
+ def setLevel(self, level, persist=False, ignoreRestrict=False):
if (level < 0 or level > 5) and level is not None:
raise ValueError(str(level) + " is not a valid value for level")
@@ -356,7 +356,9 @@ class Skill(HandledItem):
self.activeLevel = level
- if eos.config.settings['strictSkillLevels']:
+ # todo: have a way to do bulk skill level editing. Currently, everytime a single skill is changed, this runs,
+ # which affects performance. Should have a checkSkillLevels() or something that is more efficient for bulk.
+ if not ignoreRestrict and eos.config.settings['strictSkillLevels']:
start = time.time()
for item, rlevel in self.item.requiredFor.iteritems():
if item.group.category.ID == 16: # Skill category
diff --git a/eve.db b/eve.db
index 82a8cf198..f0345c126 100644
Binary files a/eve.db and b/eve.db differ
diff --git a/gui/builtinContextMenus/amount.py b/gui/builtinContextMenus/amount.py
index f9dd957ff..e4a76948d 100644
--- a/gui/builtinContextMenus/amount.py
+++ b/gui/builtinContextMenus/amount.py
@@ -4,6 +4,7 @@ import gui.mainFrame
import gui.globalEvents as GE
# noinspection PyPackageRequirements
import wx
+import re
from service.fit import Fit
from eos.saveddata.cargo import Cargo as es_Cargo
from eos.saveddata.fighter import Fighter as es_Fighter
@@ -61,15 +62,16 @@ class AmountChanger(wx.Dialog):
return
sFit = Fit.getInstance()
+ cleanInput = re.sub(r'[^0-9.]', '', self.input.GetLineText(0).strip())
mainFrame = gui.mainFrame.MainFrame.getInstance()
fitID = mainFrame.getActiveFit()
if isinstance(self.thing, es_Cargo):
- sFit.addCargo(fitID, self.thing.item.ID, int(float(self.input.GetLineText(0))), replace=True)
+ sFit.addCargo(fitID, self.thing.item.ID, int(float(cleanInput)), replace=True)
elif isinstance(self.thing, es_Fit):
- sFit.changeAmount(fitID, self.thing, int(float(self.input.GetLineText(0))))
+ sFit.changeAmount(fitID, self.thing, int(float(cleanInput)))
elif isinstance(self.thing, es_Fighter):
- sFit.changeActiveFighters(fitID, self.thing, int(float(self.input.GetLineText(0))))
+ sFit.changeActiveFighters(fitID, self.thing, int(float(cleanInput)))
wx.PostEvent(mainFrame, GE.FitChanged(fitID=fitID))
diff --git a/gui/builtinContextMenus/metaSwap.py b/gui/builtinContextMenus/metaSwap.py
index 3e098c471..e4432abdd 100644
--- a/gui/builtinContextMenus/metaSwap.py
+++ b/gui/builtinContextMenus/metaSwap.py
@@ -14,6 +14,7 @@ from eos.saveddata.module import Module
from eos.saveddata.drone import Drone
from eos.saveddata.fighter import Fighter
from eos.saveddata.implant import Implant
+from eos.saveddata.cargo import Cargo
class MetaSwap(ContextMenu):
@@ -31,6 +32,7 @@ class MetaSwap(ContextMenu):
"fighterItem",
"boosterItem",
"implantItem",
+ "cargoItem",
):
return False
@@ -185,6 +187,13 @@ class MetaSwap(ContextMenu):
sFit.addImplant(fitID, item.ID, True)
break
+ elif isinstance(selected_item, Cargo):
+ for idx, cargo_stack in enumerate(fit.cargo):
+ if cargo_stack is selected_item:
+ sFit.removeCargo(fitID, idx)
+ sFit.addCargo(fitID, item.ID, cargo_stack.amount, True)
+ break
+
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
diff --git a/gui/builtinItemStatsViews/__init__.py b/gui/builtinItemStatsViews/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/gui/builtinItemStatsViews/helpers.py b/gui/builtinItemStatsViews/helpers.py
new file mode 100644
index 000000000..74ceee8cb
--- /dev/null
+++ b/gui/builtinItemStatsViews/helpers.py
@@ -0,0 +1,18 @@
+# noinspection PyPackageRequirements
+import wx
+
+# noinspection PyPackageRequirements
+import wx.lib.mixins.listctrl as listmix
+
+
+class AutoListCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin, listmix.ListRowHighlighter):
+ def __init__(self, parent, ID, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0):
+ wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
+ listmix.ListCtrlAutoWidthMixin.__init__(self)
+ listmix.ListRowHighlighter.__init__(self)
+
+
+class AutoListCtrlNoHighlight(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin, listmix.ListRowHighlighter):
+ def __init__(self, parent, ID, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0):
+ wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
+ listmix.ListCtrlAutoWidthMixin.__init__(self)
diff --git a/gui/builtinItemStatsViews/itemAffectedBy.py b/gui/builtinItemStatsViews/itemAffectedBy.py
new file mode 100644
index 000000000..b29d84b89
--- /dev/null
+++ b/gui/builtinItemStatsViews/itemAffectedBy.py
@@ -0,0 +1,447 @@
+# noinspection PyPackageRequirements
+import wx
+
+from eos.saveddata.mode import Mode
+from eos.saveddata.character import Skill
+from eos.saveddata.implant import Implant
+from eos.saveddata.booster import Booster
+from eos.saveddata.drone import Drone
+from eos.saveddata.fighter import Fighter
+from eos.saveddata.module import Module
+from eos.saveddata.ship import Ship
+from eos.saveddata.citadel import Citadel
+from eos.saveddata.fit import Fit
+
+import gui.mainFrame
+from gui.contextMenu import ContextMenu
+from gui.bitmapLoader import BitmapLoader
+
+
+class ItemAffectedBy(wx.Panel):
+ ORDER = [Fit, Ship, Citadel, Mode, Module, Drone, Fighter, Implant, Booster, Skill]
+
+ def __init__(self, parent, stuff, item):
+ wx.Panel.__init__(self, parent)
+ self.stuff = stuff
+ self.item = item
+
+ self.activeFit = gui.mainFrame.MainFrame.getInstance().getActiveFit()
+
+ self.showRealNames = False
+ self.showAttrView = False
+ self.expand = -1
+
+ self.treeItems = []
+
+ mainSizer = wx.BoxSizer(wx.VERTICAL)
+
+ self.affectedBy = wx.TreeCtrl(self, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT | wx.NO_BORDER)
+ mainSizer.Add(self.affectedBy, 1, wx.ALL | wx.EXPAND, 0)
+
+ self.m_staticline = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL)
+
+ mainSizer.Add(self.m_staticline, 0, wx.EXPAND)
+ bSizer = wx.BoxSizer(wx.HORIZONTAL)
+
+ self.toggleExpandBtn = wx.ToggleButton(self, wx.ID_ANY, u"Expand All", wx.DefaultPosition, wx.DefaultSize, 0)
+ bSizer.Add(self.toggleExpandBtn, 0, wx.ALIGN_CENTER_VERTICAL)
+
+ self.toggleNameBtn = wx.ToggleButton(self, wx.ID_ANY, u"Toggle Names", wx.DefaultPosition, wx.DefaultSize, 0)
+ bSizer.Add(self.toggleNameBtn, 0, wx.ALIGN_CENTER_VERTICAL)
+
+ self.toggleViewBtn = wx.ToggleButton(self, wx.ID_ANY, u"Toggle View", wx.DefaultPosition, wx.DefaultSize, 0)
+ bSizer.Add(self.toggleViewBtn, 0, wx.ALIGN_CENTER_VERTICAL)
+
+ if stuff is not None:
+ self.refreshBtn = wx.Button(self, wx.ID_ANY, u"Refresh", wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT)
+ bSizer.Add(self.refreshBtn, 0, wx.ALIGN_CENTER_VERTICAL)
+ self.refreshBtn.Bind(wx.EVT_BUTTON, self.RefreshTree)
+
+ self.toggleNameBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleNameMode)
+ self.toggleExpandBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleExpand)
+ self.toggleViewBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleViewMode)
+
+ mainSizer.Add(bSizer, 0, wx.ALIGN_RIGHT)
+ self.SetSizer(mainSizer)
+ self.PopulateTree()
+ self.Layout()
+ self.affectedBy.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.scheduleMenu)
+
+ def scheduleMenu(self, event):
+ event.Skip()
+ wx.CallAfter(self.spawnMenu, event.Item)
+
+ def spawnMenu(self, item):
+ self.affectedBy.SelectItem(item)
+
+ stuff = self.affectedBy.GetPyData(item)
+ # String is set as data when we are dealing with attributes, not stuff containers
+ if stuff is None or isinstance(stuff, basestring):
+ return
+ contexts = []
+
+ # Skills are different in that they don't have itemModifiedAttributes,
+ # which is needed if we send the container to itemStats dialog. So
+ # instead, we send the item.
+ type_ = stuff.__class__.__name__
+ contexts.append(("itemStats", type_))
+ menu = ContextMenu.getMenu(stuff if type_ != "Skill" else stuff.item, *contexts)
+ self.PopupMenu(menu)
+
+ def ExpandCollapseTree(self):
+
+ self.Freeze()
+ if self.expand == 1:
+ self.affectedBy.ExpandAll()
+ else:
+ try:
+ self.affectedBy.CollapseAll()
+ except:
+ pass
+
+ self.Thaw()
+
+ def ToggleExpand(self, event):
+ self.expand *= -1
+ self.ExpandCollapseTree()
+
+ def ToggleViewTree(self):
+ self.Freeze()
+
+ for item in self.treeItems:
+ change = self.affectedBy.GetPyData(item)
+ display = self.affectedBy.GetItemText(item)
+ self.affectedBy.SetItemText(item, change)
+ self.affectedBy.SetPyData(item, display)
+
+ self.Thaw()
+
+ def UpdateTree(self):
+ self.Freeze()
+ self.affectedBy.DeleteAllItems()
+ self.PopulateTree()
+ self.Thaw()
+
+ def RefreshTree(self, event):
+ self.UpdateTree()
+ event.Skip()
+
+ def ToggleViewMode(self, event):
+ self.showAttrView = not self.showAttrView
+ self.affectedBy.DeleteAllItems()
+ self.PopulateTree()
+ event.Skip()
+
+ def ToggleNameMode(self, event):
+ self.showRealNames = not self.showRealNames
+ self.ToggleViewTree()
+ event.Skip()
+
+ def PopulateTree(self):
+ # sheri was here
+ del self.treeItems[:]
+ root = self.affectedBy.AddRoot("WINPWNZ0R")
+ self.affectedBy.SetPyData(root, None)
+
+ self.imageList = wx.ImageList(16, 16)
+ self.affectedBy.SetImageList(self.imageList)
+
+ if self.showAttrView:
+ self.buildAttributeView(root)
+ else:
+ self.buildModuleView(root)
+
+ self.ExpandCollapseTree()
+
+ def sortAttrDisplayName(self, attr):
+ info = self.stuff.item.attributes.get(attr)
+ if info and info.displayName != "":
+ return info.displayName
+
+ return attr
+
+ def buildAttributeView(self, root):
+ """
+ We first build a usable dictionary of items. The key is either a fit
+ if the afflictions stem from a projected fit, or self.stuff if they
+ are local afflictions (everything else, even gang boosts at this time)
+ The value of this is yet another dictionary in the following format:
+
+ "attribute name": {
+ "Module Name": [
+ class of affliction,
+ affliction item (required due to GH issue #335)
+ modifier type
+ amount of modification
+ whether this affliction was projected
+ ]
+ }
+ """
+
+ attributes = self.stuff.itemModifiedAttributes if self.item == self.stuff.item else self.stuff.chargeModifiedAttributes
+ container = {}
+ for attrName in attributes.iterAfflictions():
+ # if value is 0 or there has been no change from original to modified, return
+ if attributes[attrName] == (attributes.getOriginal(attrName, 0)):
+ continue
+
+ for fit, afflictors in attributes.getAfflictions(attrName).iteritems():
+ for afflictor, modifier, amount, used in afflictors:
+
+ if not used or afflictor.item is None:
+ continue
+
+ if fit.ID != self.activeFit:
+ # affliction fit does not match our fit
+ if fit not in container:
+ container[fit] = {}
+ items = container[fit]
+ else:
+ # local afflictions
+ if self.stuff not in container:
+ container[self.stuff] = {}
+ items = container[self.stuff]
+
+ # items hold our module: info mappings
+ if attrName not in items:
+ items[attrName] = []
+
+ if afflictor == self.stuff and getattr(afflictor, 'charge', None):
+ # we are showing a charges modifications, see #335
+ item = afflictor.charge
+ else:
+ item = afflictor.item
+
+ items[attrName].append(
+ (type(afflictor), afflictor, item, modifier, amount, getattr(afflictor, "projected", False)))
+
+ # Make sure projected fits are on top
+ rootOrder = container.keys()
+ rootOrder.sort(key=lambda x: self.ORDER.index(type(x)))
+
+ # Now, we take our created dictionary and start adding stuff to our tree
+ for thing in rootOrder:
+ # This block simply directs which parent we are adding to (root or projected fit)
+ if thing == self.stuff:
+ parent = root
+ else: # projected fit
+ icon = self.imageList.Add(BitmapLoader.getBitmap("ship_small", "gui"))
+ child = self.affectedBy.AppendItem(root, "{} ({})".format(thing.name, thing.ship.item.name), icon)
+ parent = child
+
+ attributes = container[thing]
+ attrOrder = sorted(attributes.keys(), key=self.sortAttrDisplayName)
+
+ for attrName in attrOrder:
+ attrInfo = self.stuff.item.attributes.get(attrName)
+ displayName = attrInfo.displayName if attrInfo and attrInfo.displayName != "" else attrName
+
+ if attrInfo:
+ if attrInfo.icon is not None:
+ iconFile = attrInfo.icon.iconFile
+ icon = BitmapLoader.getBitmap(iconFile, "icons")
+ if icon is None:
+ icon = BitmapLoader.getBitmap("transparent16x16", "gui")
+ attrIcon = self.imageList.Add(icon)
+ else:
+ attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons"))
+ else:
+ attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons"))
+
+ if self.showRealNames:
+ display = attrName
+ saved = displayName
+ else:
+ display = displayName
+ saved = attrName
+
+ # this is the attribute node
+ child = self.affectedBy.AppendItem(parent, display, attrIcon)
+ self.affectedBy.SetPyData(child, saved)
+ self.treeItems.append(child)
+
+ items = attributes[attrName]
+ items.sort(key=lambda x: self.ORDER.index(x[0]))
+ for itemInfo in items:
+ afflictorType, afflictor, item, attrModifier, attrAmount, projected = itemInfo
+
+ if afflictorType == Ship:
+ itemIcon = self.imageList.Add(BitmapLoader.getBitmap("ship_small", "gui"))
+ elif item.icon:
+ bitmap = BitmapLoader.getBitmap(item.icon.iconFile, "icons")
+ itemIcon = self.imageList.Add(bitmap) if bitmap else -1
+ else:
+ itemIcon = -1
+
+ displayStr = item.name
+
+ if projected:
+ displayStr += " (projected)"
+
+ penalized = ""
+ if '*' in attrModifier:
+ if 's' in attrModifier:
+ penalized += "(penalized)"
+ if 'r' in attrModifier:
+ penalized += "(resisted)"
+ attrModifier = "*"
+
+ # this is the Module node, the attribute will be attached to this
+ display = "%s %s %.2f %s" % (displayStr, attrModifier, attrAmount, penalized)
+ treeItem = self.affectedBy.AppendItem(child, display, itemIcon)
+ self.affectedBy.SetPyData(treeItem, afflictor)
+
+ def buildModuleView(self, root):
+ """
+ We first build a usable dictionary of items. The key is either a fit
+ if the afflictions stem from a projected fit, or self.stuff if they
+ are local afflictions (everything else, even gang boosts at this time)
+ The value of this is yet another dictionary in the following format:
+
+ "Module Name": [
+ class of affliction,
+ set of afflictors (such as 2 of the same module),
+ info on affliction (attribute name, modifier, and modification amount),
+ item that will be used to determine icon (required due to GH issue #335)
+ whether this affliction is actually used (unlearned skills are not used)
+ ]
+ """
+
+ attributes = self.stuff.itemModifiedAttributes if self.item == self.stuff.item else self.stuff.chargeModifiedAttributes
+ container = {}
+ for attrName in attributes.iterAfflictions():
+ # if value is 0 or there has been no change from original to modified, return
+ if attributes[attrName] == (attributes.getOriginal(attrName, 0)):
+ continue
+
+ for fit, afflictors in attributes.getAfflictions(attrName).iteritems():
+ for afflictor, modifier, amount, used in afflictors:
+ if not used or getattr(afflictor, 'item', None) is None:
+ continue
+
+ if fit.ID != self.activeFit:
+ # affliction fit does not match our fit
+ if fit not in container:
+ container[fit] = {}
+ items = container[fit]
+ else:
+ # local afflictions
+ if self.stuff not in container:
+ container[self.stuff] = {}
+ items = container[self.stuff]
+
+ if afflictor == self.stuff and getattr(afflictor, 'charge', None):
+ # we are showing a charges modifications, see #335
+ item = afflictor.charge
+ else:
+ item = afflictor.item
+
+ # items hold our module: info mappings
+ if item.name not in items:
+ items[item.name] = [type(afflictor), set(), [], item, getattr(afflictor, "projected", False)]
+
+ info = items[item.name]
+ info[1].add(afflictor)
+ # If info[1] > 1, there are two separate modules working.
+ # Check to make sure we only include the modifier once
+ # See GH issue 154
+ if len(info[1]) > 1 and (attrName, modifier, amount) in info[2]:
+ continue
+ info[2].append((attrName, modifier, amount))
+
+ # Make sure projected fits are on top
+ rootOrder = container.keys()
+ rootOrder.sort(key=lambda x: self.ORDER.index(type(x)))
+
+ # Now, we take our created dictionary and start adding stuff to our tree
+ for thing in rootOrder:
+ # This block simply directs which parent we are adding to (root or projected fit)
+ if thing == self.stuff:
+ parent = root
+ else: # projected fit
+ icon = self.imageList.Add(BitmapLoader.getBitmap("ship_small", "gui"))
+ child = self.affectedBy.AppendItem(root, "{} ({})".format(thing.name, thing.ship.item.name), icon)
+ parent = child
+
+ items = container[thing]
+ order = items.keys()
+ order.sort(key=lambda x: (self.ORDER.index(items[x][0]), x))
+
+ for itemName in order:
+ info = items[itemName]
+ afflictorType, afflictors, attrData, item, projected = info
+ counter = len(afflictors)
+ if afflictorType == Ship:
+ itemIcon = self.imageList.Add(BitmapLoader.getBitmap("ship_small", "gui"))
+ elif item.icon:
+ bitmap = BitmapLoader.getBitmap(item.icon.iconFile, "icons")
+ itemIcon = self.imageList.Add(bitmap) if bitmap else -1
+ else:
+ itemIcon = -1
+
+ displayStr = itemName
+
+ if counter > 1:
+ displayStr += " x {}".format(counter)
+
+ if projected:
+ displayStr += " (projected)"
+
+ # this is the Module node, the attribute will be attached to this
+ child = self.affectedBy.AppendItem(parent, displayStr, itemIcon)
+ self.affectedBy.SetPyData(child, afflictors.pop())
+
+ if counter > 0:
+ attributes = []
+ for attrName, attrModifier, attrAmount in attrData:
+ attrInfo = self.stuff.item.attributes.get(attrName)
+ displayName = attrInfo.displayName if attrInfo else ""
+
+ if attrInfo:
+ if attrInfo.icon is not None:
+ iconFile = attrInfo.icon.iconFile
+ icon = BitmapLoader.getBitmap(iconFile, "icons")
+ if icon is None:
+ icon = BitmapLoader.getBitmap("transparent16x16", "gui")
+
+ attrIcon = self.imageList.Add(icon)
+ else:
+ attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons"))
+ else:
+ attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons"))
+
+ penalized = ""
+ if '*' in attrModifier:
+ if 's' in attrModifier:
+ penalized += "(penalized)"
+ if 'r' in attrModifier:
+ penalized += "(resisted)"
+ attrModifier = "*"
+
+ attributes.append((attrName, (displayName if displayName != "" else attrName), attrModifier,
+ attrAmount, penalized, attrIcon))
+
+ attrSorted = sorted(attributes, key=lambda attribName: attribName[0])
+ for attr in attrSorted:
+ attrName, displayName, attrModifier, attrAmount, penalized, attrIcon = attr
+
+ 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
+ )
+ else:
+ 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)
+ self.affectedBy.SetPyData(treeitem, saved)
+ self.treeItems.append(treeitem)
diff --git a/gui/builtinItemStatsViews/itemAttributes.py b/gui/builtinItemStatsViews/itemAttributes.py
new file mode 100644
index 000000000..1e1804d1d
--- /dev/null
+++ b/gui/builtinItemStatsViews/itemAttributes.py
@@ -0,0 +1,273 @@
+import sys
+import csv
+import config
+
+# noinspection PyPackageRequirements
+import wx
+
+from helpers import AutoListCtrl
+
+from gui.bitmapLoader import BitmapLoader
+from service.market import Market
+from service.attribute import Attribute
+from gui.utils.numberFormatter import formatAmount
+
+
+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)
+ mainSizer.Add(self.paramList, 1, wx.ALL | wx.EXPAND, 0)
+ self.SetSizer(mainSizer)
+
+ self.toggleView = 1
+ self.stuff = stuff
+ self.item = item
+ self.attrInfo = {}
+ self.attrValues = {}
+ self._fetchValues()
+
+ self.m_staticline = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL)
+ mainSizer.Add(self.m_staticline, 0, wx.EXPAND)
+ bSizer = wx.BoxSizer(wx.HORIZONTAL)
+
+ 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)
+ 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)
+ bSizer.Add(self.exportStatsBtn, 0, wx.ALIGN_CENTER_VERTICAL)
+
+ if stuff is not None:
+ self.refreshBtn = wx.Button(self, wx.ID_ANY, u"Refresh", wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT)
+ bSizer.Add(self.refreshBtn, 0, wx.ALIGN_CENTER_VERTICAL)
+ self.refreshBtn.Bind(wx.EVT_BUTTON, self.RefreshValues)
+
+ mainSizer.Add(bSizer, 0, wx.ALIGN_RIGHT)
+
+ self.PopulateList()
+
+ self.toggleViewBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleViewMode)
+ self.exportStatsBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ExportItemStats)
+
+ def _fetchValues(self):
+ if self.stuff is None:
+ self.attrInfo.clear()
+ self.attrValues.clear()
+ self.attrInfo.update(self.item.attributes)
+ self.attrValues.update(self.item.attributes)
+ elif self.stuff.item == self.item:
+ self.attrInfo.clear()
+ self.attrValues.clear()
+ self.attrInfo.update(self.stuff.item.attributes)
+ self.attrValues.update(self.stuff.itemModifiedAttributes)
+ elif self.stuff.charge == self.item:
+ self.attrInfo.clear()
+ self.attrValues.clear()
+ self.attrInfo.update(self.stuff.charge.attributes)
+ self.attrValues.update(self.stuff.chargeModifiedAttributes)
+ # When item for stats window no longer exists, don't change anything
+ else:
+ return
+
+ def UpdateList(self):
+ self.Freeze()
+ self.paramList.ClearAll()
+ self.PopulateList()
+ self.Thaw()
+ self.paramList.resizeLastColumn(100)
+
+ def RefreshValues(self, event):
+ self._fetchValues()
+ self.UpdateList()
+ event.Skip()
+
+ def ToggleViewMode(self, event):
+ self.toggleView *= -1
+ self.UpdateList()
+ event.Skip()
+
+ def ExportItemStats(self, event):
+ exportFileName = self.item.name + " (" + str(self.item.ID) + ").csv"
+
+ saveFileDialog = wx.FileDialog(self, "Save CSV file", "", exportFileName,
+ "CSV files (*.csv)|*.csv", wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
+
+ if saveFileDialog.ShowModal() == wx.ID_CANCEL:
+ return # the user hit cancel...
+
+ with open(saveFileDialog.GetPath(), "wb") as exportFile:
+ writer = csv.writer(exportFile, delimiter=',')
+
+ writer.writerow(
+ [
+ "ID",
+ "Internal Name",
+ "Friendly Name",
+ "Modified Value",
+ "Base Value",
+ ]
+ )
+
+ for attribute in self.attrValues:
+
+ try:
+ attribute_id = self.attrInfo[attribute].ID
+ except (KeyError, AttributeError):
+ attribute_id = ''
+
+ try:
+ attribute_name = self.attrInfo[attribute].name
+ except (KeyError, AttributeError):
+ attribute_name = attribute
+
+ try:
+ attribute_displayname = self.attrInfo[attribute].displayName
+ except (KeyError, AttributeError):
+ attribute_displayname = ''
+
+ try:
+ attribute_value = self.attrInfo[attribute].value
+ except (KeyError, AttributeError):
+ attribute_value = ''
+
+ try:
+ attribute_modified_value = self.attrValues[attribute].value
+ except (KeyError, AttributeError):
+ attribute_modified_value = self.attrValues[attribute]
+
+ writer.writerow(
+ [
+ attribute_id,
+ attribute_name,
+ attribute_displayname,
+ attribute_modified_value,
+ attribute_value,
+ ]
+ )
+
+ def PopulateList(self):
+ self.paramList.InsertColumn(0, "Attribute")
+ self.paramList.InsertColumn(1, "Current Value")
+ if self.stuff is not None:
+ self.paramList.InsertColumn(2, "Base Value")
+ self.paramList.SetColumnWidth(0, 110)
+ self.paramList.SetColumnWidth(1, 90)
+ if self.stuff is not None:
+ self.paramList.SetColumnWidth(2, 90)
+ self.paramList.setResizeColumn(0)
+ self.imageList = wx.ImageList(16, 16)
+ self.paramList.SetImageList(self.imageList, wx.IMAGE_LIST_SMALL)
+
+ names = list(self.attrValues.iterkeys())
+ names.sort()
+
+ idNameMap = {}
+ idCount = 0
+ for name in names:
+ info = self.attrInfo.get(name)
+ att = self.attrValues[name]
+
+ valDefault = getattr(info, "value", None)
+ valueDefault = valDefault if valDefault is not None else att
+
+ val = getattr(att, "value", None)
+ value = val if val is not None else att
+
+ if info and info.displayName and self.toggleView == 1:
+ attrName = info.displayName
+ else:
+ attrName = name
+
+ if info and config.debug:
+ attrName += " ({})".format(info.ID)
+
+ if info:
+ if info.icon is not None:
+ iconFile = info.icon.iconFile
+ icon = BitmapLoader.getBitmap(iconFile, "icons")
+
+ if icon is None:
+ icon = BitmapLoader.getBitmap("transparent16x16", "gui")
+
+ attrIcon = self.imageList.Add(icon)
+ else:
+ attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons"))
+ else:
+ attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons"))
+
+ index = self.paramList.InsertImageStringItem(sys.maxint, attrName, attrIcon)
+ idNameMap[idCount] = attrName
+ self.paramList.SetItemData(index, idCount)
+ idCount += 1
+
+ if self.toggleView != 1:
+ valueUnit = str(value)
+ elif info and info.unit:
+ valueUnit = self.TranslateValueUnit(value, info.unit.displayName, info.unit.name)
+ else:
+ valueUnit = formatAmount(value, 3, 0, 0)
+
+ if self.toggleView != 1:
+ valueUnitDefault = str(valueDefault)
+ elif info and info.unit:
+ valueUnitDefault = self.TranslateValueUnit(valueDefault, info.unit.displayName, info.unit.name)
+ else:
+ valueUnitDefault = formatAmount(valueDefault, 3, 0, 0)
+
+ self.paramList.SetStringItem(index, 1, valueUnit)
+ if self.stuff is not None:
+ self.paramList.SetStringItem(index, 2, valueUnitDefault)
+
+ self.paramList.SortItems(lambda id1, id2: cmp(idNameMap[id1], idNameMap[id2]))
+ self.paramList.RefreshRows()
+ self.totalAttrsLabel.SetLabel("%d attributes. " % idCount)
+ self.Layout()
+
+ @staticmethod
+ def TranslateValueUnit(value, unitName, unitDisplayName):
+ def itemIDCallback():
+ item = Market.getInstance().getItem(value)
+ return "%s (%d)" % (item.name, value) if item is not None else str(value)
+
+ def groupIDCallback():
+ group = Market.getInstance().getGroup(value)
+ return "%s (%d)" % (group.name, value) if group is not None else str(value)
+
+ def attributeIDCallback():
+ attribute = Attribute.getInstance().getAttributeInfo(value)
+ return "%s (%d)" % (attribute.name.capitalize(), value)
+
+ 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),
+ "Volume" : (lambda: value, u"m\u00B3"),
+ "Sizeclass" : (lambda: value, ""),
+ "Absolute Percent" : (lambda: (value * 100), unitName),
+ "Milliseconds" : (lambda: value / 1000.0, unitName),
+ "typeID" : (itemIDCallback, ""),
+ "groupID" : (groupIDCallback, ""),
+ "attributeID" : (attributeIDCallback, "")
+ }
+
+ override = trans.get(unitDisplayName)
+ if override is not None:
+ v = override[0]()
+ if isinstance(v, str):
+ fvalue = v
+ elif isinstance(v, (int, float, long)):
+ fvalue = formatAmount(v, 3, 0, 0)
+ else:
+ fvalue = v
+ return "%s %s" % (fvalue, override[1])
+ else:
+ return "%s %s" % (formatAmount(value, 3, 0), unitName)
diff --git a/gui/builtinItemStatsViews/itemCompare.py b/gui/builtinItemStatsViews/itemCompare.py
new file mode 100644
index 000000000..4e560883e
--- /dev/null
+++ b/gui/builtinItemStatsViews/itemCompare.py
@@ -0,0 +1,208 @@
+import sys
+
+# noinspection PyPackageRequirements
+import wx
+
+from helpers import AutoListCtrl
+from service.price import Price as ServicePrice
+from service.market import Market
+from service.attribute import Attribute
+from gui.utils.numberFormatter import formatAmount
+
+
+class ItemCompare(wx.Panel):
+ def __init__(self, parent, stuff, item, items, context=None):
+ # Start dealing with Price stuff to get that thread going
+ sPrice = ServicePrice.getInstance()
+ sPrice.getPrices(items, self.UpdateList)
+
+ 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)
+ mainSizer.Add(self.paramList, 1, wx.ALL | wx.EXPAND, 0)
+ self.SetSizer(mainSizer)
+
+ self.toggleView = 1
+ self.stuff = stuff
+ 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.attrs = {}
+
+ # get a dict of attrName: attrInfo of all unique attributes across all items
+ for item in self.items:
+ for attr in item.attributes.keys():
+ if item.attributes[attr].info.displayName:
+ self.attrs[attr] = item.attributes[attr].info
+
+ # Process attributes for items and find ones that differ
+ for attr in self.attrs.keys():
+ value = None
+
+ for item in self.items:
+ # we can automatically break here if this item doesn't have the attribute,
+ # as that means at least one item did
+ if attr not in item.attributes:
+ break
+
+ # this is the first attribute for the item set, set the initial value
+ if value is None:
+ value = item.attributes[attr].value
+ continue
+
+ if attr not in item.attributes or item.attributes[attr].value != value:
+ break
+ else:
+ # attribute values were all the same, delete
+ del self.attrs[attr]
+
+ self.m_staticline = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize,
+ wx.LI_HORIZONTAL)
+ mainSizer.Add(self.m_staticline, 0, wx.EXPAND)
+ bSizer = wx.BoxSizer(wx.HORIZONTAL)
+
+ 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)
+ bSizer.Add(self.toggleViewBtn, 0, wx.ALIGN_CENTER_VERTICAL)
+
+ self.refreshBtn = wx.Button(self, wx.ID_ANY, u"Refresh", wx.DefaultPosition, wx.DefaultSize,
+ wx.BU_EXACTFIT)
+ bSizer.Add(self.refreshBtn, 0, wx.ALIGN_CENTER_VERTICAL)
+ self.refreshBtn.Bind(wx.EVT_BUTTON, self.RefreshValues)
+
+ mainSizer.Add(bSizer, 0, wx.ALIGN_RIGHT)
+
+ self.PopulateList()
+
+ self.toggleViewBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleViewMode)
+ self.Bind(wx.EVT_LIST_COL_CLICK, self.SortCompareCols)
+
+ def SortCompareCols(self, event):
+ self.Freeze()
+ self.paramList.ClearAll()
+ self.PopulateList(event.Column)
+ self.Thaw()
+
+ def UpdateList(self, items=None):
+ # We do nothing with `items`, but it gets returned by the price service thread
+ self.Freeze()
+ self.paramList.ClearAll()
+ self.PopulateList()
+ self.Thaw()
+ self.paramList.resizeLastColumn(100)
+
+ def RefreshValues(self, event):
+ self.UpdateList()
+ event.Skip()
+
+ def ToggleViewMode(self, event):
+ self.toggleView *= -1
+ self.UpdateList()
+ event.Skip()
+
+ def processPrices(self, prices):
+ for i, price in enumerate(prices):
+ self.paramList.SetStringItem(i, len(self.attrs) + 1, formatAmount(price.value, 3, 3, 9, currency=True))
+
+ def PopulateList(self, sort=None):
+
+ if sort is not None and self.currentSort == sort:
+ self.sortReverse = not self.sortReverse
+ else:
+ self.currentSort = sort
+ self.sortReverse = False
+
+ if sort is not None:
+ if sort == 0: # Name sort
+ func = lambda _val: _val.name
+ else:
+ try:
+ # Remember to reduce by 1, because the attrs array
+ # starts at 0 while the list has the item name as column 0.
+ attr = str(self.attrs.keys()[sort - 1])
+ func = lambda _val: _val.attributes[attr].value if attr in _val.attributes else None
+ except IndexError:
+ # Clicked on a column that's not part of our array (price most likely)
+ self.sortReverse = False
+ func = lambda _val: _val.attributes['metaLevel'].value if 'metaLevel' in _val.attributes else None
+
+ self.items = sorted(self.items, key=func, reverse=self.sortReverse)
+
+ self.paramList.InsertColumn(0, "Item")
+ self.paramList.SetColumnWidth(0, 200)
+
+ for i, attr in enumerate(self.attrs.keys()):
+ name = self.attrs[attr].displayName if self.attrs[attr].displayName else attr
+ self.paramList.InsertColumn(i + 1, name)
+ self.paramList.SetColumnWidth(i + 1, 120)
+
+ self.paramList.InsertColumn(len(self.attrs) + 1, "Price")
+ self.paramList.SetColumnWidth(len(self.attrs) + 1, 60)
+
+ for item in self.items:
+ i = self.paramList.InsertStringItem(sys.maxint, item.name)
+ for x, attr in enumerate(self.attrs.keys()):
+ if attr in item.attributes:
+ info = self.attrs[attr]
+ value = item.attributes[attr].value
+ if self.toggleView != 1:
+ valueUnit = str(value)
+ elif info and info.unit and self.toggleView == 1:
+ valueUnit = self.TranslateValueUnit(value, info.unit.displayName, info.unit.name)
+ else:
+ valueUnit = formatAmount(value, 3, 0, 0)
+
+ self.paramList.SetStringItem(i, x + 1, valueUnit)
+
+ # Add prices
+ self.paramList.SetStringItem(i, len(self.attrs) + 1, formatAmount(item.price.price, 3, 3, 9, currency=True))
+
+ self.paramList.RefreshRows()
+ self.Layout()
+
+ @staticmethod
+ def TranslateValueUnit(value, unitName, unitDisplayName):
+ def itemIDCallback():
+ item = Market.getInstance().getItem(value)
+ return "%s (%d)" % (item.name, value) if item is not None else str(value)
+
+ def groupIDCallback():
+ group = Market.getInstance().getGroup(value)
+ return "%s (%d)" % (group.name, value) if group is not None else str(value)
+
+ def attributeIDCallback():
+ attribute = Attribute.getInstance().getAttributeInfo(value)
+ return "%s (%d)" % (attribute.name.capitalize(), value)
+
+ 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),
+ "Volume" : (lambda: value, u"m\u00B3"),
+ "Sizeclass" : (lambda: value, ""),
+ "Absolute Percent" : (lambda: (value * 100), unitName),
+ "Milliseconds" : (lambda: value / 1000.0, unitName),
+ "typeID" : (itemIDCallback, ""),
+ "groupID" : (groupIDCallback, ""),
+ "attributeID" : (attributeIDCallback, "")
+ }
+
+ override = trans.get(unitDisplayName)
+ if override is not None:
+ v = override[0]()
+ if isinstance(v, str):
+ fvalue = v
+ elif isinstance(v, (int, float, long)):
+ fvalue = formatAmount(v, 3, 0, 0)
+ else:
+ fvalue = v
+ return "%s %s" % (fvalue, override[1])
+ else:
+ return "%s %s" % (formatAmount(value, 3, 0), unitName)
diff --git a/gui/builtinItemStatsViews/itemDependants.py b/gui/builtinItemStatsViews/itemDependants.py
new file mode 100644
index 000000000..3a8110b55
--- /dev/null
+++ b/gui/builtinItemStatsViews/itemDependants.py
@@ -0,0 +1,53 @@
+# noinspection PyPackageRequirements
+import wx
+
+from gui.bitmapLoader import BitmapLoader
+
+
+class ItemDependents(wx.Panel):
+ def __init__(self, parent, stuff, item):
+ wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL)
+
+ # itemId is set by the parent.
+ self.romanNb = ["0", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X"]
+ self.skillIdHistory = []
+ mainSizer = wx.BoxSizer(wx.VERTICAL)
+
+ self.reqTree = wx.TreeCtrl(self, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT | wx.NO_BORDER)
+
+ mainSizer.Add(self.reqTree, 1, wx.ALL | wx.EXPAND, 0)
+
+ self.SetSizer(mainSizer)
+ self.root = self.reqTree.AddRoot("WINRARZOR")
+ self.reqTree.SetPyData(self.root, None)
+
+ self.imageList = wx.ImageList(16, 16)
+ self.reqTree.SetImageList(self.imageList)
+ skillBookId = self.imageList.Add(BitmapLoader.getBitmap("skill_small", "gui"))
+
+ self.getFullSkillTree(item, self.root, skillBookId)
+
+ self.Layout()
+
+ def getFullSkillTree(self, parentSkill, parent, sbIconId):
+ levelToItems = {}
+
+ for item, level in parentSkill.requiredFor.iteritems():
+ if level not in levelToItems:
+ levelToItems[level] = []
+ levelToItems[level].append(item)
+
+ for x in sorted(levelToItems.keys()):
+ items = levelToItems[x]
+ items.sort(key=lambda x: x.name)
+
+ child = self.reqTree.AppendItem(parent, "Level {}".format(self.romanNb[int(x)]), sbIconId)
+ for item in items:
+
+ if item.icon:
+ bitmap = BitmapLoader.getBitmap(item.icon.iconFile, "icons")
+ itemIcon = self.imageList.Add(bitmap) if bitmap else -1
+ else:
+ itemIcon = -1
+
+ self.reqTree.AppendItem(child, "{}".format(item.name), itemIcon)
diff --git a/gui/builtinItemStatsViews/itemDescription.py b/gui/builtinItemStatsViews/itemDescription.py
new file mode 100644
index 000000000..c94bd2b66
--- /dev/null
+++ b/gui/builtinItemStatsViews/itemDescription.py
@@ -0,0 +1,33 @@
+# noinspection PyPackageRequirements
+import wx
+# noinspection PyPackageRequirements
+import wx.html
+import re
+
+
+class ItemDescription(wx.Panel):
+ def __init__(self, parent, stuff, item):
+ wx.Panel.__init__(self, parent)
+ mainSizer = wx.BoxSizer(wx.VERTICAL)
+ self.SetSizer(mainSizer)
+
+ bgcolor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)
+ fgcolor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOWTEXT)
+
+ self.description = wx.html.HtmlWindow(self)
+
+ if not item.description:
+ return
+
+ desc = item.description.replace("\n", "
")
+ # Strip font tags
+ desc = re.sub("<( *)font( *)color( *)=(.*?)>(?P.*?)<( *)/( *)font( *)>", "\g", desc)
+ # Strip URLs
+ desc = re.sub("<( *)a(.*?)>(?P.*?)<( *)/( *)a( *)>", "\g", desc)
+ desc = "" + desc + ""
+
+ self.description.SetPage(desc)
+
+ mainSizer.Add(self.description, 1, wx.ALL | wx.EXPAND, 0)
+ self.Layout()
diff --git a/gui/builtinItemStatsViews/itemEffects.py b/gui/builtinItemStatsViews/itemEffects.py
new file mode 100644
index 000000000..81617fdc6
--- /dev/null
+++ b/gui/builtinItemStatsViews/itemEffects.py
@@ -0,0 +1,130 @@
+import sys
+import os
+import subprocess
+import config
+
+# noinspection PyPackageRequirements
+import wx
+
+from helpers import AutoListCtrl
+
+
+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)
+ mainSizer.Add(self.effectList, 1, wx.ALL | wx.EXPAND, 0)
+ self.SetSizer(mainSizer)
+
+ self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnClick, self.effectList)
+ if config.debug:
+ self.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnRightClick, self.effectList)
+
+ self.PopulateList()
+
+ def PopulateList(self):
+
+ self.effectList.InsertColumn(0, "Name")
+ self.effectList.InsertColumn(1, "Active")
+ self.effectList.InsertColumn(2, "Type")
+ if config.debug:
+ self.effectList.InsertColumn(3, "Run Time")
+ self.effectList.InsertColumn(4, "ID")
+
+ # self.effectList.SetColumnWidth(0,385)
+
+ self.effectList.setResizeColumn(0)
+ self.effectList.SetColumnWidth(1, 50)
+ self.effectList.SetColumnWidth(2, 80)
+ if config.debug:
+ self.effectList.SetColumnWidth(3, 65)
+ self.effectList.SetColumnWidth(4, 40)
+
+ item = self.item
+ effects = item.effects
+ names = list(effects.iterkeys())
+ names.sort()
+
+ for name in names:
+ index = self.effectList.InsertStringItem(sys.maxint, name)
+
+ if effects[name].isImplemented:
+ if effects[name].activeByDefault:
+ activeByDefault = "Yes"
+ else:
+ activeByDefault = "No"
+ else:
+ activeByDefault = ""
+
+ effectTypeText = ""
+ if effects[name].type:
+ for effectType in effects[name].type:
+ effectTypeText += effectType + " "
+ pass
+
+ if effects[name].runTime and effects[name].isImplemented:
+ effectRunTime = str(effects[name].runTime)
+ else:
+ effectRunTime = ""
+
+ self.effectList.SetStringItem(index, 1, activeByDefault)
+ self.effectList.SetStringItem(index, 2, effectTypeText)
+ if config.debug:
+ self.effectList.SetStringItem(index, 3, effectRunTime)
+ self.effectList.SetStringItem(index, 4, str(effects[name].ID))
+
+ self.effectList.RefreshRows()
+ self.Layout()
+
+ def OnClick(self, event):
+ """
+ Debug use: toggle effects on/off.
+ Affects *ALL* items that use that effect.
+ Is not stateful. Will reset if Pyfa is closed and reopened.
+ """
+
+ try:
+ activeByDefault = getattr(self.item.effects[event.GetText()], "activeByDefault")
+ if activeByDefault:
+ setattr(self.item.effects[event.GetText()], "activeByDefault", False)
+ else:
+ setattr(self.item.effects[event.GetText()], "activeByDefault", True)
+
+ except AttributeError:
+ # Attribute doesn't exist, do nothing
+ pass
+
+ self.RefreshValues(event)
+
+ @staticmethod
+ def OnRightClick(event):
+ """
+ Debug use: open effect file with default application.
+ If effect file does not exist, create it
+ """
+
+ file_ = os.path.join(config.pyfaPath, "eos", "effects", "%s.py" % event.GetText().lower())
+
+ if not os.path.isfile(file_):
+ open(file_, 'a').close()
+
+ if 'wxMSW' in wx.PlatformInfo:
+ os.startfile(file_)
+ elif 'wxMac' in wx.PlatformInfo:
+ os.system("open " + file_)
+ else:
+ subprocess.call(["xdg-open", file_])
+
+ def RefreshValues(self, event):
+ self.Freeze()
+ self.effectList.ClearAll()
+ self.PopulateList()
+ self.effectList.RefreshRows()
+ self.Layout()
+ self.Thaw()
+ event.Skip()
diff --git a/gui/builtinItemStatsViews/itemProperties.py b/gui/builtinItemStatsViews/itemProperties.py
new file mode 100644
index 000000000..b8ce86dd5
--- /dev/null
+++ b/gui/builtinItemStatsViews/itemProperties.py
@@ -0,0 +1,99 @@
+import sys
+
+# noinspection PyPackageRequirements
+import wx
+
+from helpers import AutoListCtrl
+
+
+class ItemProperties(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)
+ mainSizer.Add(self.paramList, 1, wx.ALL | wx.EXPAND, 0)
+ self.SetSizer(mainSizer)
+
+ self.toggleView = 1
+ self.stuff = stuff
+ self.item = item
+ self.attrInfo = {}
+ self.attrValues = {}
+ self._fetchValues()
+
+ self.m_staticline = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL)
+ mainSizer.Add(self.m_staticline, 0, wx.EXPAND)
+ bSizer = wx.BoxSizer(wx.HORIZONTAL)
+
+ 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)
+
+ mainSizer.Add(bSizer, 0, wx.ALIGN_RIGHT)
+
+ self.PopulateList()
+
+ def _fetchValues(self):
+ if self.stuff is None:
+ self.attrInfo.clear()
+ self.attrValues.clear()
+ self.attrInfo.update(self.item.attributes)
+ self.attrValues.update(self.item.attributes)
+ elif self.stuff.item == self.item:
+ self.attrInfo.clear()
+ self.attrValues.clear()
+ self.attrInfo.update(self.stuff.item.attributes)
+ self.attrValues.update(self.stuff.itemModifiedAttributes)
+ elif self.stuff.charge == self.item:
+ self.attrInfo.clear()
+ self.attrValues.clear()
+ self.attrInfo.update(self.stuff.charge.attributes)
+ self.attrValues.update(self.stuff.chargeModifiedAttributes)
+ # When item for stats window no longer exists, don't change anything
+ else:
+ return
+
+ def PopulateList(self):
+ self.paramList.InsertColumn(0, "Attribute")
+ self.paramList.InsertColumn(1, "Current Value")
+ self.paramList.SetColumnWidth(0, 110)
+ self.paramList.SetColumnWidth(1, 1500)
+ self.paramList.setResizeColumn(0)
+
+ if self.stuff:
+ names = dir(self.stuff)
+ else:
+ names = dir(self.item)
+
+ names = [a for a in names if not (a.startswith('__') and a.endswith('__'))]
+
+ idNameMap = {}
+ idCount = 0
+ for name in names:
+ try:
+ if self.stuff:
+ attrName = name.title()
+ value = getattr(self.stuff, name)
+ else:
+ attrName = name.title()
+ value = getattr(self.item, name)
+
+ index = self.paramList.InsertStringItem(sys.maxint, attrName)
+ # index = self.paramList.InsertImageStringItem(sys.maxint, attrName)
+ idNameMap[idCount] = attrName
+ self.paramList.SetItemData(index, idCount)
+ idCount += 1
+
+ valueUnit = str(value)
+
+ self.paramList.SetStringItem(index, 1, valueUnit)
+ except:
+ # TODO: Add logging to this.
+ # We couldn't get a property for some reason. Skip it for now.
+ continue
+
+ self.paramList.SortItems(lambda id1, id2: cmp(idNameMap[id1], idNameMap[id2]))
+ self.paramList.RefreshRows()
+ self.totalAttrsLabel.SetLabel("%d attributes. " % idCount)
+ self.Layout()
diff --git a/gui/builtinItemStatsViews/itemRequirements.py b/gui/builtinItemStatsViews/itemRequirements.py
new file mode 100644
index 000000000..ff493bcab
--- /dev/null
+++ b/gui/builtinItemStatsViews/itemRequirements.py
@@ -0,0 +1,39 @@
+# noinspection PyPackageRequirements
+import wx
+
+from gui.bitmapLoader import BitmapLoader
+
+
+class ItemRequirements(wx.Panel):
+ def __init__(self, parent, stuff, item):
+ wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL)
+
+ # itemId is set by the parent.
+ self.romanNb = ["0", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X"]
+ self.skillIdHistory = []
+ mainSizer = wx.BoxSizer(wx.VERTICAL)
+
+ self.reqTree = wx.TreeCtrl(self, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT | wx.NO_BORDER)
+
+ mainSizer.Add(self.reqTree, 1, wx.ALL | wx.EXPAND, 0)
+
+ self.SetSizer(mainSizer)
+ self.root = self.reqTree.AddRoot("WINRARZOR")
+ self.reqTree.SetPyData(self.root, None)
+
+ self.imageList = wx.ImageList(16, 16)
+ self.reqTree.SetImageList(self.imageList)
+ skillBookId = self.imageList.Add(BitmapLoader.getBitmap("skill_small", "gui"))
+
+ self.getFullSkillTree(item, self.root, skillBookId)
+
+ self.reqTree.ExpandAll()
+
+ self.Layout()
+
+ def getFullSkillTree(self, parentSkill, parent, sbIconId):
+ for skill, level in parentSkill.requiredSkills.iteritems():
+ child = self.reqTree.AppendItem(parent, "%s %s" % (skill.name, self.romanNb[int(level)]), sbIconId)
+ if skill.ID not in self.skillIdHistory:
+ self.getFullSkillTree(skill, child, sbIconId)
+ self.skillIdHistory.append(skill.ID)
diff --git a/gui/builtinItemStatsViews/itemTraits.py b/gui/builtinItemStatsViews/itemTraits.py
new file mode 100644
index 000000000..12abd078d
--- /dev/null
+++ b/gui/builtinItemStatsViews/itemTraits.py
@@ -0,0 +1,17 @@
+# noinspection PyPackageRequirements
+import wx
+# noinspection PyPackageRequirements
+import wx.html
+
+
+class ItemTraits(wx.Panel):
+ def __init__(self, parent, stuff, item):
+ wx.Panel.__init__(self, parent)
+ mainSizer = wx.BoxSizer(wx.VERTICAL)
+ self.SetSizer(mainSizer)
+
+ self.traits = wx.html.HtmlWindow(self)
+ self.traits.SetPage(item.traits.traitText)
+
+ mainSizer.Add(self.traits, 1, wx.ALL | wx.EXPAND, 0)
+ self.Layout()
diff --git a/gui/builtinStatsViews/miningyieldViewFull.py b/gui/builtinStatsViews/miningyieldViewFull.py
index be69be316..912bddc2c 100644
--- a/gui/builtinStatsViews/miningyieldViewFull.py
+++ b/gui/builtinStatsViews/miningyieldViewFull.py
@@ -122,6 +122,8 @@ class MiningYieldViewFull(StatsView):
self.parent.views.append(view)
# Get the TogglePanel
tp = self.panel.GetParent()
+ # Bind the new panel's children to allow context menu access
+ self.parent.applyBinding(self.parent, tp.GetContentPane())
tp.SetLabel(view.getHeaderText(fit))
view.refreshPanel(fit)
diff --git a/gui/characterEditor.py b/gui/characterEditor.py
index ca3aacb70..90f955fcb 100644
--- a/gui/characterEditor.py
+++ b/gui/characterEditor.py
@@ -35,6 +35,9 @@ from service.character import Character
from service.network import AuthenticationError, TimeoutError
from service.market import Market
from logbook import Logger
+
+from gui.utils.clipboard import toClipboard, fromClipboard
+
pyfalog = Logger(__name__)
@@ -199,6 +202,7 @@ class CharacterEditor(wx.Frame):
self.btnSaveChar.Enable(not char.ro and char.isDirty)
self.btnSaveAs.Enable(char.isDirty)
self.btnRevert.Enable(char.isDirty)
+ self.sview.importBtn.Enable(not char.ro)
def refreshCharacterList(self, event=None):
"""This is only called when we save a modified character"""
@@ -333,6 +337,26 @@ class SkillTreeView(wx.Panel):
bSizerButtons = wx.BoxSizer(wx.HORIZONTAL)
bSizerButtons.Add(self.btnSecStatus, 0, wx.ALL, 5)
+
+ bSizerButtons.AddSpacer((0, 0), 1, wx.EXPAND, 5)
+
+ importExport = (("Import", wx.ART_FILE_OPEN, "from"),
+ ("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)
+
+ btn.SetMinSize(btn.GetSize())
+ btn.SetMaxSize(btn.GetSize())
+
+ btn.Layout()
+ setattr(self, "{}Btn".format(name.lower()), btn)
+ btn.Enable(True)
+ btn.SetToolTipString("%s skills %s clipboard" % (name, direction))
+ bSizerButtons.Add(btn, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_RIGHT | wx.ALL, 5)
+ btn.Bind(wx.EVT_BUTTON, getattr(self, "{}Skills".format(name.lower())))
+
pmainSizer.Add(bSizerButtons, 0, wx.EXPAND, 5)
# bind the Character selection event
@@ -368,6 +392,48 @@ class SkillTreeView(wx.Panel):
self.Layout()
+ def importSkills(self, evt):
+
+ dlg = wx.MessageDialog(self, "Importing skills into this character will set the skill levels as pending. " +
+ "To save the skills permanently, please click the Save button at the bottom of the window after importing"
+ , "Import Skills", wx.OK)
+ dlg.ShowModal()
+ dlg.Destroy()
+
+ text = fromClipboard().strip()
+ if text:
+ char = self.charEditor.entityEditor.getActiveEntity()
+ try:
+ lines = text.splitlines()
+
+ for l in lines:
+ skill, level = l.strip()[:-1].strip(), int(l.strip()[-1])
+ skill = char.getSkill(skill)
+ if skill:
+ skill.setLevel(level, ignoreRestrict=True)
+
+ except Exception as e:
+ dlg = wx.MessageDialog(self, "There was an error importing skills, please see log file", "Error", wx.ICON_ERROR)
+ dlg.ShowModal()
+ dlg.Destroy()
+ pyfalog.error(e)
+
+ finally:
+ self.charEditor.btnRestrict()
+ self.populateSkillTree()
+ self.charEditor.entityEditor.refreshEntityList(char)
+
+ def exportSkills(self, evt):
+ char = self.charEditor.entityEditor.getActiveEntity()
+
+ skills = sorted(char.__class__.getSkillNameMap().keys())
+ list = ""
+ for s in skills:
+ skill = char.getSkill(s)
+ list += "{} {}\n".format(skill.item.name, skill.level)
+
+ toClipboard(list)
+
def onSecStatus(self, event):
sChar = Character.getInstance()
char = self.charEditor.entityEditor.getActiveEntity()
diff --git a/gui/characterSelection.py b/gui/characterSelection.py
index 13df550e8..7a1bfcb7a 100644
--- a/gui/characterSelection.py
+++ b/gui/characterSelection.py
@@ -19,12 +19,15 @@
# noinspection PyPackageRequirements
import wx
-from gui.bitmapLoader import BitmapLoader
+
+from logbook import Logger
+
import gui.globalEvents as GE
import gui.mainFrame
+from gui.bitmapLoader import BitmapLoader
+from gui.utils.clipboard import toClipboard
from service.character import Character
from service.fit import Fit
-from logbook import Logger
pyfalog = Logger(__name__)
@@ -94,6 +97,9 @@ class CharacterSelection(wx.Panel):
grantItem = menu.Append(wx.ID_ANY, "Grant Missing Skills")
self.Bind(wx.EVT_MENU, self.grantMissingSkills, grantItem)
+ exportItem = menu.Append(wx.ID_ANY, "Export Missing Skills")
+ self.Bind(wx.EVT_MENU, self.exportSkills, exportItem)
+
self.PopupMenu(menu, pos)
event.Skip()
@@ -246,6 +252,15 @@ class CharacterSelection(wx.Panel):
event.Skip()
+ def exportSkills(self, evt):
+ skillsMap = self._buildSkillsTooltipCondensed(self.reqs, skillsMap={})
+
+ list = ""
+ for key in sorted(skillsMap):
+ list += "%s %d\n" % (key, skillsMap[key])
+
+ toClipboard(list)
+
def _buildSkillsTooltip(self, reqs, currItem="", tabulationLevel=0):
tip = ""
sCharacter = Character.getInstance()
diff --git a/gui/itemStats.py b/gui/itemStats.py
index e8f4c85a5..dec1bc12c 100644
--- a/gui/itemStats.py
+++ b/gui/itemStats.py
@@ -17,37 +17,23 @@
# along with pyfa. If not, see .
# =============================================================================
-import re
-import os
-import csv
-import sys
-import subprocess
-
# noinspection PyPackageRequirements
import wx
-# noinspection PyPackageRequirements
-import wx.html
-# noinspection PyPackageRequirements
-import wx.lib.mixins.listctrl as listmix
import config
-from eos.saveddata.mode import Mode
-from eos.saveddata.character import Skill
-from eos.saveddata.implant import Implant
-from eos.saveddata.booster import Booster
-from eos.saveddata.drone import Drone
-from eos.saveddata.fighter import Fighter
-from eos.saveddata.module import Module
-from eos.saveddata.ship import Ship
-from eos.saveddata.citadel import Citadel
-from eos.saveddata.fit import Fit
from service.market import Market
-from service.attribute import Attribute
-from service.price import Price as ServicePrice
import gui.mainFrame
from gui.bitmapLoader import BitmapLoader
-from gui.utils.numberFormatter import formatAmount
-from gui.contextMenu import ContextMenu
+
+from gui.builtinItemStatsViews.itemTraits import ItemTraits
+from gui.builtinItemStatsViews.itemDescription import ItemDescription
+from gui.builtinItemStatsViews.itemAttributes import ItemParams
+from gui.builtinItemStatsViews.itemCompare import ItemCompare
+from gui.builtinItemStatsViews.itemRequirements import ItemRequirements
+from gui.builtinItemStatsViews.itemDependants import ItemDependents
+from gui.builtinItemStatsViews.itemEffects import ItemEffects
+from gui.builtinItemStatsViews.itemAffectedBy import ItemAffectedBy
+from gui.builtinItemStatsViews.itemProperties import ItemProperties
class ItemStatsDialog(wx.Dialog):
@@ -217,1243 +203,3 @@ class ItemStatsContainer(wx.Panel):
tab, _ = self.nbContainer.HitTest(event.Position)
if tab != -1:
self.nbContainer.SetSelection(tab)
-
-
-class AutoListCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin, listmix.ListRowHighlighter):
- def __init__(self, parent, ID, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0):
- wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
- listmix.ListCtrlAutoWidthMixin.__init__(self)
- listmix.ListRowHighlighter.__init__(self)
-
-
-class AutoListCtrlNoHighlight(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin, listmix.ListRowHighlighter):
- def __init__(self, parent, ID, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0):
- wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
- listmix.ListCtrlAutoWidthMixin.__init__(self)
-
-
-class ItemTraits(wx.Panel):
- def __init__(self, parent, stuff, item):
- wx.Panel.__init__(self, parent)
- mainSizer = wx.BoxSizer(wx.VERTICAL)
- self.SetSizer(mainSizer)
-
- self.traits = wx.html.HtmlWindow(self)
- self.traits.SetPage(item.traits.traitText)
-
- mainSizer.Add(self.traits, 1, wx.ALL | wx.EXPAND, 0)
- self.Layout()
-
-
-class ItemDescription(wx.Panel):
- def __init__(self, parent, stuff, item):
- wx.Panel.__init__(self, parent)
- mainSizer = wx.BoxSizer(wx.VERTICAL)
- self.SetSizer(mainSizer)
-
- bgcolor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)
- fgcolor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOWTEXT)
-
- self.description = wx.html.HtmlWindow(self)
-
- if not item.description:
- return
-
- desc = item.description.replace("\n", "
")
- # Strip font tags
- desc = re.sub("<( *)font( *)color( *)=(.*?)>(?P.*?)<( *)/( *)font( *)>", "\g", desc)
- # Strip URLs
- desc = re.sub("<( *)a(.*?)>(?P.*?)<( *)/( *)a( *)>", "\g", desc)
- desc = "" + desc + ""
-
- self.description.SetPage(desc)
-
- mainSizer.Add(self.description, 1, wx.ALL | wx.EXPAND, 0)
- self.Layout()
-
-
-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)
- mainSizer.Add(self.paramList, 1, wx.ALL | wx.EXPAND, 0)
- self.SetSizer(mainSizer)
-
- self.toggleView = 1
- self.stuff = stuff
- self.item = item
- self.attrInfo = {}
- self.attrValues = {}
- self._fetchValues()
-
- self.m_staticline = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL)
- mainSizer.Add(self.m_staticline, 0, wx.EXPAND)
- bSizer = wx.BoxSizer(wx.HORIZONTAL)
-
- 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)
- 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)
- bSizer.Add(self.exportStatsBtn, 0, wx.ALIGN_CENTER_VERTICAL)
-
- if stuff is not None:
- self.refreshBtn = wx.Button(self, wx.ID_ANY, u"Refresh", wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT)
- bSizer.Add(self.refreshBtn, 0, wx.ALIGN_CENTER_VERTICAL)
- self.refreshBtn.Bind(wx.EVT_BUTTON, self.RefreshValues)
-
- mainSizer.Add(bSizer, 0, wx.ALIGN_RIGHT)
-
- self.PopulateList()
-
- self.toggleViewBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleViewMode)
- self.exportStatsBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ExportItemStats)
-
- def _fetchValues(self):
- if self.stuff is None:
- self.attrInfo.clear()
- self.attrValues.clear()
- self.attrInfo.update(self.item.attributes)
- self.attrValues.update(self.item.attributes)
- elif self.stuff.item == self.item:
- self.attrInfo.clear()
- self.attrValues.clear()
- self.attrInfo.update(self.stuff.item.attributes)
- self.attrValues.update(self.stuff.itemModifiedAttributes)
- elif self.stuff.charge == self.item:
- self.attrInfo.clear()
- self.attrValues.clear()
- self.attrInfo.update(self.stuff.charge.attributes)
- self.attrValues.update(self.stuff.chargeModifiedAttributes)
- # When item for stats window no longer exists, don't change anything
- else:
- return
-
- def UpdateList(self):
- self.Freeze()
- self.paramList.ClearAll()
- self.PopulateList()
- self.Thaw()
- self.paramList.resizeLastColumn(100)
-
- def RefreshValues(self, event):
- self._fetchValues()
- self.UpdateList()
- event.Skip()
-
- def ToggleViewMode(self, event):
- self.toggleView *= -1
- self.UpdateList()
- event.Skip()
-
- def ExportItemStats(self, event):
- exportFileName = self.item.name + " (" + str(self.item.ID) + ").csv"
-
- saveFileDialog = wx.FileDialog(self, "Save CSV file", "", exportFileName,
- "CSV files (*.csv)|*.csv", wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
-
- if saveFileDialog.ShowModal() == wx.ID_CANCEL:
- return # the user hit cancel...
-
- with open(saveFileDialog.GetPath(), "wb") as exportFile:
- writer = csv.writer(exportFile, delimiter=',')
-
- writer.writerow(
- [
- "ID",
- "Internal Name",
- "Friendly Name",
- "Modified Value",
- "Base Value",
- ]
- )
-
- for attribute in self.attrValues:
-
- try:
- attribute_id = self.attrInfo[attribute].ID
- except (KeyError, AttributeError):
- attribute_id = ''
-
- try:
- attribute_name = self.attrInfo[attribute].name
- except (KeyError, AttributeError):
- attribute_name = attribute
-
- try:
- attribute_displayname = self.attrInfo[attribute].displayName
- except (KeyError, AttributeError):
- attribute_displayname = ''
-
- try:
- attribute_value = self.attrInfo[attribute].value
- except (KeyError, AttributeError):
- attribute_value = ''
-
- try:
- attribute_modified_value = self.attrValues[attribute].value
- except (KeyError, AttributeError):
- attribute_modified_value = self.attrValues[attribute]
-
- writer.writerow(
- [
- attribute_id,
- attribute_name,
- attribute_displayname,
- attribute_modified_value,
- attribute_value,
- ]
- )
-
- def PopulateList(self):
- self.paramList.InsertColumn(0, "Attribute")
- self.paramList.InsertColumn(1, "Current Value")
- if self.stuff is not None:
- self.paramList.InsertColumn(2, "Base Value")
- self.paramList.SetColumnWidth(0, 110)
- self.paramList.SetColumnWidth(1, 90)
- if self.stuff is not None:
- self.paramList.SetColumnWidth(2, 90)
- self.paramList.setResizeColumn(0)
- self.imageList = wx.ImageList(16, 16)
- self.paramList.SetImageList(self.imageList, wx.IMAGE_LIST_SMALL)
-
- names = list(self.attrValues.iterkeys())
- names.sort()
-
- idNameMap = {}
- idCount = 0
- for name in names:
- info = self.attrInfo.get(name)
- att = self.attrValues[name]
-
- valDefault = getattr(info, "value", None)
- valueDefault = valDefault if valDefault is not None else att
-
- val = getattr(att, "value", None)
- value = val if val is not None else att
-
- if info and info.displayName and self.toggleView == 1:
- attrName = info.displayName
- else:
- attrName = name
-
- if info and config.debug:
- attrName += " ({})".format(info.ID)
-
- if info:
- if info.icon is not None:
- iconFile = info.icon.iconFile
- icon = BitmapLoader.getBitmap(iconFile, "icons")
-
- if icon is None:
- icon = BitmapLoader.getBitmap("transparent16x16", "gui")
-
- attrIcon = self.imageList.Add(icon)
- else:
- attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons"))
- else:
- attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons"))
-
- index = self.paramList.InsertImageStringItem(sys.maxint, attrName, attrIcon)
- idNameMap[idCount] = attrName
- self.paramList.SetItemData(index, idCount)
- idCount += 1
-
- if self.toggleView != 1:
- valueUnit = str(value)
- elif info and info.unit:
- valueUnit = self.TranslateValueUnit(value, info.unit.displayName, info.unit.name)
- else:
- valueUnit = formatAmount(value, 3, 0, 0)
-
- if self.toggleView != 1:
- valueUnitDefault = str(valueDefault)
- elif info and info.unit:
- valueUnitDefault = self.TranslateValueUnit(valueDefault, info.unit.displayName, info.unit.name)
- else:
- valueUnitDefault = formatAmount(valueDefault, 3, 0, 0)
-
- self.paramList.SetStringItem(index, 1, valueUnit)
- if self.stuff is not None:
- self.paramList.SetStringItem(index, 2, valueUnitDefault)
-
- self.paramList.SortItems(lambda id1, id2: cmp(idNameMap[id1], idNameMap[id2]))
- self.paramList.RefreshRows()
- self.totalAttrsLabel.SetLabel("%d attributes. " % idCount)
- self.Layout()
-
- @staticmethod
- def TranslateValueUnit(value, unitName, unitDisplayName):
- def itemIDCallback():
- item = Market.getInstance().getItem(value)
- return "%s (%d)" % (item.name, value) if item is not None else str(value)
-
- def groupIDCallback():
- group = Market.getInstance().getGroup(value)
- return "%s (%d)" % (group.name, value) if group is not None else str(value)
-
- def attributeIDCallback():
- attribute = Attribute.getInstance().getAttributeInfo(value)
- return "%s (%d)" % (attribute.name.capitalize(), value)
-
- 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),
- "Volume" : (lambda: value, u"m\u00B3"),
- "Sizeclass" : (lambda: value, ""),
- "Absolute Percent" : (lambda: (value * 100), unitName),
- "Milliseconds" : (lambda: value / 1000.0, unitName),
- "typeID" : (itemIDCallback, ""),
- "groupID" : (groupIDCallback, ""),
- "attributeID" : (attributeIDCallback, "")
- }
-
- override = trans.get(unitDisplayName)
- if override is not None:
- v = override[0]()
- if isinstance(v, str):
- fvalue = v
- elif isinstance(v, (int, float, long)):
- fvalue = formatAmount(v, 3, 0, 0)
- else:
- fvalue = v
- return "%s %s" % (fvalue, override[1])
- else:
- return "%s %s" % (formatAmount(value, 3, 0), unitName)
-
-
-class ItemCompare(wx.Panel):
- def __init__(self, parent, stuff, item, items, context=None):
- # Start dealing with Price stuff to get that thread going
- sPrice = ServicePrice.getInstance()
- sPrice.getPrices(items, self.UpdateList)
-
- 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)
- mainSizer.Add(self.paramList, 1, wx.ALL | wx.EXPAND, 0)
- self.SetSizer(mainSizer)
-
- self.toggleView = 1
- self.stuff = stuff
- 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.attrs = {}
-
- # get a dict of attrName: attrInfo of all unique attributes across all items
- for item in self.items:
- for attr in item.attributes.keys():
- if item.attributes[attr].info.displayName:
- self.attrs[attr] = item.attributes[attr].info
-
- # Process attributes for items and find ones that differ
- for attr in self.attrs.keys():
- value = None
-
- for item in self.items:
- # we can automatically break here if this item doesn't have the attribute,
- # as that means at least one item did
- if attr not in item.attributes:
- break
-
- # this is the first attribute for the item set, set the initial value
- if value is None:
- value = item.attributes[attr].value
- continue
-
- if attr not in item.attributes or item.attributes[attr].value != value:
- break
- else:
- # attribute values were all the same, delete
- del self.attrs[attr]
-
- self.m_staticline = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize,
- wx.LI_HORIZONTAL)
- mainSizer.Add(self.m_staticline, 0, wx.EXPAND)
- bSizer = wx.BoxSizer(wx.HORIZONTAL)
-
- 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)
- bSizer.Add(self.toggleViewBtn, 0, wx.ALIGN_CENTER_VERTICAL)
-
- self.refreshBtn = wx.Button(self, wx.ID_ANY, u"Refresh", wx.DefaultPosition, wx.DefaultSize,
- wx.BU_EXACTFIT)
- bSizer.Add(self.refreshBtn, 0, wx.ALIGN_CENTER_VERTICAL)
- self.refreshBtn.Bind(wx.EVT_BUTTON, self.RefreshValues)
-
- mainSizer.Add(bSizer, 0, wx.ALIGN_RIGHT)
-
- self.PopulateList()
-
- self.toggleViewBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleViewMode)
- self.Bind(wx.EVT_LIST_COL_CLICK, self.SortCompareCols)
-
- def SortCompareCols(self, event):
- self.Freeze()
- self.paramList.ClearAll()
- self.PopulateList(event.Column)
- self.Thaw()
-
- def UpdateList(self, items=None):
- # We do nothing with `items`, but it gets returned by the price service thread
- self.Freeze()
- self.paramList.ClearAll()
- self.PopulateList()
- self.Thaw()
- self.paramList.resizeLastColumn(100)
-
- def RefreshValues(self, event):
- self.UpdateList()
- event.Skip()
-
- def ToggleViewMode(self, event):
- self.toggleView *= -1
- self.UpdateList()
- event.Skip()
-
- def processPrices(self, prices):
- for i, price in enumerate(prices):
- self.paramList.SetStringItem(i, len(self.attrs) + 1, formatAmount(price.value, 3, 3, 9, currency=True))
-
- def PopulateList(self, sort=None):
-
- if sort is not None and self.currentSort == sort:
- self.sortReverse = not self.sortReverse
- else:
- self.currentSort = sort
- self.sortReverse = False
-
- if sort is not None:
- if sort == 0: # Name sort
- func = lambda _val: _val.name
- else:
- try:
- # Remember to reduce by 1, because the attrs array
- # starts at 0 while the list has the item name as column 0.
- attr = str(self.attrs.keys()[sort - 1])
- func = lambda _val: _val.attributes[attr].value if attr in _val.attributes else None
- except IndexError:
- # Clicked on a column that's not part of our array (price most likely)
- self.sortReverse = False
- func = lambda _val: _val.attributes['metaLevel'].value if 'metaLevel' in _val.attributes else None
-
- self.items = sorted(self.items, key=func, reverse=self.sortReverse)
-
- self.paramList.InsertColumn(0, "Item")
- self.paramList.SetColumnWidth(0, 200)
-
- for i, attr in enumerate(self.attrs.keys()):
- name = self.attrs[attr].displayName if self.attrs[attr].displayName else attr
- self.paramList.InsertColumn(i + 1, name)
- self.paramList.SetColumnWidth(i + 1, 120)
-
- self.paramList.InsertColumn(len(self.attrs) + 1, "Price")
- self.paramList.SetColumnWidth(len(self.attrs) + 1, 60)
-
- for item in self.items:
- i = self.paramList.InsertStringItem(sys.maxint, item.name)
- for x, attr in enumerate(self.attrs.keys()):
- if attr in item.attributes:
- info = self.attrs[attr]
- value = item.attributes[attr].value
- if self.toggleView != 1:
- valueUnit = str(value)
- elif info and info.unit and self.toggleView == 1:
- valueUnit = self.TranslateValueUnit(value, info.unit.displayName, info.unit.name)
- else:
- valueUnit = formatAmount(value, 3, 0, 0)
-
- self.paramList.SetStringItem(i, x + 1, valueUnit)
-
- # Add prices
- self.paramList.SetStringItem(i, len(self.attrs) + 1, formatAmount(item.price.price, 3, 3, 9, currency=True))
-
- self.paramList.RefreshRows()
- self.Layout()
-
- @staticmethod
- def TranslateValueUnit(value, unitName, unitDisplayName):
- def itemIDCallback():
- item = Market.getInstance().getItem(value)
- return "%s (%d)" % (item.name, value) if item is not None else str(value)
-
- def groupIDCallback():
- group = Market.getInstance().getGroup(value)
- return "%s (%d)" % (group.name, value) if group is not None else str(value)
-
- def attributeIDCallback():
- attribute = Attribute.getInstance().getAttributeInfo(value)
- return "%s (%d)" % (attribute.name.capitalize(), value)
-
- 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),
- "Volume" : (lambda: value, u"m\u00B3"),
- "Sizeclass" : (lambda: value, ""),
- "Absolute Percent" : (lambda: (value * 100), unitName),
- "Milliseconds" : (lambda: value / 1000.0, unitName),
- "typeID" : (itemIDCallback, ""),
- "groupID" : (groupIDCallback, ""),
- "attributeID" : (attributeIDCallback, "")
- }
-
- override = trans.get(unitDisplayName)
- if override is not None:
- v = override[0]()
- if isinstance(v, str):
- fvalue = v
- elif isinstance(v, (int, float, long)):
- fvalue = formatAmount(v, 3, 0, 0)
- else:
- fvalue = v
- return "%s %s" % (fvalue, override[1])
- else:
- return "%s %s" % (formatAmount(value, 3, 0), unitName)
-
-
-class ItemRequirements(wx.Panel):
- def __init__(self, parent, stuff, item):
- wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL)
-
- # itemId is set by the parent.
- self.romanNb = ["0", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X"]
- self.skillIdHistory = []
- mainSizer = wx.BoxSizer(wx.VERTICAL)
-
- self.reqTree = wx.TreeCtrl(self, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT | wx.NO_BORDER)
-
- mainSizer.Add(self.reqTree, 1, wx.ALL | wx.EXPAND, 0)
-
- self.SetSizer(mainSizer)
- self.root = self.reqTree.AddRoot("WINRARZOR")
- self.reqTree.SetPyData(self.root, None)
-
- self.imageList = wx.ImageList(16, 16)
- self.reqTree.SetImageList(self.imageList)
- skillBookId = self.imageList.Add(BitmapLoader.getBitmap("skill_small", "gui"))
-
- self.getFullSkillTree(item, self.root, skillBookId)
-
- self.reqTree.ExpandAll()
-
- self.Layout()
-
- def getFullSkillTree(self, parentSkill, parent, sbIconId):
- for skill, level in parentSkill.requiredSkills.iteritems():
- child = self.reqTree.AppendItem(parent, "%s %s" % (skill.name, self.romanNb[int(level)]), sbIconId)
- if skill.ID not in self.skillIdHistory:
- self.getFullSkillTree(skill, child, sbIconId)
- self.skillIdHistory.append(skill.ID)
-
-
-class ItemDependents(wx.Panel):
- def __init__(self, parent, stuff, item):
- wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL)
-
- # itemId is set by the parent.
- self.romanNb = ["0", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X"]
- self.skillIdHistory = []
- mainSizer = wx.BoxSizer(wx.VERTICAL)
-
- self.reqTree = wx.TreeCtrl(self, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT | wx.NO_BORDER)
-
- mainSizer.Add(self.reqTree, 1, wx.ALL | wx.EXPAND, 0)
-
- self.SetSizer(mainSizer)
- self.root = self.reqTree.AddRoot("WINRARZOR")
- self.reqTree.SetPyData(self.root, None)
-
- self.imageList = wx.ImageList(16, 16)
- self.reqTree.SetImageList(self.imageList)
- skillBookId = self.imageList.Add(BitmapLoader.getBitmap("skill_small", "gui"))
-
- self.getFullSkillTree(item, self.root, skillBookId)
-
- self.Layout()
-
- def getFullSkillTree(self, parentSkill, parent, sbIconId):
- levelToItems = {}
-
- for item, level in parentSkill.requiredFor.iteritems():
- if level not in levelToItems:
- levelToItems[level] = []
- levelToItems[level].append(item)
-
- for x in sorted(levelToItems.keys()):
- items = levelToItems[x]
- items.sort(key=lambda x: x.name)
-
- child = self.reqTree.AppendItem(parent, "Level {}".format(self.romanNb[int(x)]), sbIconId)
- for item in items:
-
- if item.icon:
- bitmap = BitmapLoader.getBitmap(item.icon.iconFile, "icons")
- itemIcon = self.imageList.Add(bitmap) if bitmap else -1
- else:
- itemIcon = -1
-
- self.reqTree.AppendItem(child, "{}".format(item.name), itemIcon)
-
-
-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)
- mainSizer.Add(self.effectList, 1, wx.ALL | wx.EXPAND, 0)
- self.SetSizer(mainSizer)
-
- self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnClick, self.effectList)
- if config.debug:
- self.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnRightClick, self.effectList)
-
- self.PopulateList()
-
- def PopulateList(self):
-
- self.effectList.InsertColumn(0, "Name")
- self.effectList.InsertColumn(1, "Active")
- self.effectList.InsertColumn(2, "Type")
- if config.debug:
- self.effectList.InsertColumn(3, "Run Time")
- self.effectList.InsertColumn(4, "ID")
-
- # self.effectList.SetColumnWidth(0,385)
-
- self.effectList.setResizeColumn(0)
- self.effectList.SetColumnWidth(1, 50)
- self.effectList.SetColumnWidth(2, 80)
- if config.debug:
- self.effectList.SetColumnWidth(3, 65)
- self.effectList.SetColumnWidth(4, 40)
-
- item = self.item
- effects = item.effects
- names = list(effects.iterkeys())
- names.sort()
-
- for name in names:
- index = self.effectList.InsertStringItem(sys.maxint, name)
-
- if effects[name].isImplemented:
- if effects[name].activeByDefault:
- activeByDefault = "Yes"
- else:
- activeByDefault = "No"
- else:
- activeByDefault = ""
-
- effectTypeText = ""
- if effects[name].type:
- for effectType in effects[name].type:
- effectTypeText += effectType + " "
- pass
-
- if effects[name].runTime and effects[name].isImplemented:
- effectRunTime = str(effects[name].runTime)
- else:
- effectRunTime = ""
-
- self.effectList.SetStringItem(index, 1, activeByDefault)
- self.effectList.SetStringItem(index, 2, effectTypeText)
- if config.debug:
- self.effectList.SetStringItem(index, 3, effectRunTime)
- self.effectList.SetStringItem(index, 4, str(effects[name].ID))
-
- self.effectList.RefreshRows()
- self.Layout()
-
- def OnClick(self, event):
- """
- Debug use: toggle effects on/off.
- Affects *ALL* items that use that effect.
- Is not stateful. Will reset if Pyfa is closed and reopened.
- """
-
- try:
- activeByDefault = getattr(self.item.effects[event.GetText()], "activeByDefault")
- if activeByDefault:
- setattr(self.item.effects[event.GetText()], "activeByDefault", False)
- else:
- setattr(self.item.effects[event.GetText()], "activeByDefault", True)
-
- except AttributeError:
- # Attribute doesn't exist, do nothing
- pass
-
- self.RefreshValues(event)
-
- @staticmethod
- def OnRightClick(event):
- """
- Debug use: open effect file with default application.
- If effect file does not exist, create it
- """
-
- file_ = os.path.join(config.pyfaPath, "eos", "effects", "%s.py" % event.GetText().lower())
-
- if not os.path.isfile(file_):
- open(file_, 'a').close()
-
- if 'wxMSW' in wx.PlatformInfo:
- os.startfile(file_)
- elif 'wxMac' in wx.PlatformInfo:
- os.system("open " + file_)
- else:
- subprocess.call(["xdg-open", file_])
-
- def RefreshValues(self, event):
- self.Freeze()
- self.effectList.ClearAll()
- self.PopulateList()
- self.effectList.RefreshRows()
- self.Layout()
- self.Thaw()
- event.Skip()
-
-
-class ItemAffectedBy(wx.Panel):
- ORDER = [Fit, Ship, Citadel, Mode, Module, Drone, Fighter, Implant, Booster, Skill]
-
- def __init__(self, parent, stuff, item):
- wx.Panel.__init__(self, parent)
- self.stuff = stuff
- self.item = item
-
- self.activeFit = gui.mainFrame.MainFrame.getInstance().getActiveFit()
-
- self.showRealNames = False
- self.showAttrView = False
- self.expand = -1
-
- self.treeItems = []
-
- mainSizer = wx.BoxSizer(wx.VERTICAL)
-
- self.affectedBy = wx.TreeCtrl(self, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT | wx.NO_BORDER)
- mainSizer.Add(self.affectedBy, 1, wx.ALL | wx.EXPAND, 0)
-
- self.m_staticline = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL)
-
- mainSizer.Add(self.m_staticline, 0, wx.EXPAND)
- bSizer = wx.BoxSizer(wx.HORIZONTAL)
-
- self.toggleExpandBtn = wx.ToggleButton(self, wx.ID_ANY, u"Expand All", wx.DefaultPosition, wx.DefaultSize, 0)
- bSizer.Add(self.toggleExpandBtn, 0, wx.ALIGN_CENTER_VERTICAL)
-
- self.toggleNameBtn = wx.ToggleButton(self, wx.ID_ANY, u"Toggle Names", wx.DefaultPosition, wx.DefaultSize, 0)
- bSizer.Add(self.toggleNameBtn, 0, wx.ALIGN_CENTER_VERTICAL)
-
- self.toggleViewBtn = wx.ToggleButton(self, wx.ID_ANY, u"Toggle View", wx.DefaultPosition, wx.DefaultSize, 0)
- bSizer.Add(self.toggleViewBtn, 0, wx.ALIGN_CENTER_VERTICAL)
-
- if stuff is not None:
- self.refreshBtn = wx.Button(self, wx.ID_ANY, u"Refresh", wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT)
- bSizer.Add(self.refreshBtn, 0, wx.ALIGN_CENTER_VERTICAL)
- self.refreshBtn.Bind(wx.EVT_BUTTON, self.RefreshTree)
-
- self.toggleNameBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleNameMode)
- self.toggleExpandBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleExpand)
- self.toggleViewBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleViewMode)
-
- mainSizer.Add(bSizer, 0, wx.ALIGN_RIGHT)
- self.SetSizer(mainSizer)
- self.PopulateTree()
- self.Layout()
- self.affectedBy.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.scheduleMenu)
-
- def scheduleMenu(self, event):
- event.Skip()
- wx.CallAfter(self.spawnMenu, event.Item)
-
- def spawnMenu(self, item):
- self.affectedBy.SelectItem(item)
-
- stuff = self.affectedBy.GetPyData(item)
- # String is set as data when we are dealing with attributes, not stuff containers
- if stuff is None or isinstance(stuff, basestring):
- return
- contexts = []
-
- # Skills are different in that they don't have itemModifiedAttributes,
- # which is needed if we send the container to itemStats dialog. So
- # instead, we send the item.
- type_ = stuff.__class__.__name__
- contexts.append(("itemStats", type_))
- menu = ContextMenu.getMenu(stuff if type_ != "Skill" else stuff.item, *contexts)
- self.PopupMenu(menu)
-
- def ExpandCollapseTree(self):
-
- self.Freeze()
- if self.expand == 1:
- self.affectedBy.ExpandAll()
- else:
- try:
- self.affectedBy.CollapseAll()
- except:
- pass
-
- self.Thaw()
-
- def ToggleExpand(self, event):
- self.expand *= -1
- self.ExpandCollapseTree()
-
- def ToggleViewTree(self):
- self.Freeze()
-
- for item in self.treeItems:
- change = self.affectedBy.GetPyData(item)
- display = self.affectedBy.GetItemText(item)
- self.affectedBy.SetItemText(item, change)
- self.affectedBy.SetPyData(item, display)
-
- self.Thaw()
-
- def UpdateTree(self):
- self.Freeze()
- self.affectedBy.DeleteAllItems()
- self.PopulateTree()
- self.Thaw()
-
- def RefreshTree(self, event):
- self.UpdateTree()
- event.Skip()
-
- def ToggleViewMode(self, event):
- self.showAttrView = not self.showAttrView
- self.affectedBy.DeleteAllItems()
- self.PopulateTree()
- event.Skip()
-
- def ToggleNameMode(self, event):
- self.showRealNames = not self.showRealNames
- self.ToggleViewTree()
- event.Skip()
-
- def PopulateTree(self):
- # sheri was here
- del self.treeItems[:]
- root = self.affectedBy.AddRoot("WINPWNZ0R")
- self.affectedBy.SetPyData(root, None)
-
- self.imageList = wx.ImageList(16, 16)
- self.affectedBy.SetImageList(self.imageList)
-
- if self.showAttrView:
- self.buildAttributeView(root)
- else:
- self.buildModuleView(root)
-
- self.ExpandCollapseTree()
-
- def sortAttrDisplayName(self, attr):
- info = self.stuff.item.attributes.get(attr)
- if info and info.displayName != "":
- return info.displayName
-
- return attr
-
- def buildAttributeView(self, root):
- """
- We first build a usable dictionary of items. The key is either a fit
- if the afflictions stem from a projected fit, or self.stuff if they
- are local afflictions (everything else, even gang boosts at this time)
- The value of this is yet another dictionary in the following format:
-
- "attribute name": {
- "Module Name": [
- class of affliction,
- affliction item (required due to GH issue #335)
- modifier type
- amount of modification
- whether this affliction was projected
- ]
- }
- """
-
- attributes = self.stuff.itemModifiedAttributes if self.item == self.stuff.item else self.stuff.chargeModifiedAttributes
- container = {}
- for attrName in attributes.iterAfflictions():
- # if value is 0 or there has been no change from original to modified, return
- if attributes[attrName] == (attributes.getOriginal(attrName, 0)):
- continue
-
- for fit, afflictors in attributes.getAfflictions(attrName).iteritems():
- for afflictor, modifier, amount, used in afflictors:
-
- if not used or afflictor.item is None:
- continue
-
- if fit.ID != self.activeFit:
- # affliction fit does not match our fit
- if fit not in container:
- container[fit] = {}
- items = container[fit]
- else:
- # local afflictions
- if self.stuff not in container:
- container[self.stuff] = {}
- items = container[self.stuff]
-
- # items hold our module: info mappings
- if attrName not in items:
- items[attrName] = []
-
- if afflictor == self.stuff and getattr(afflictor, 'charge', None):
- # we are showing a charges modifications, see #335
- item = afflictor.charge
- else:
- item = afflictor.item
-
- items[attrName].append(
- (type(afflictor), afflictor, item, modifier, amount, getattr(afflictor, "projected", False)))
-
- # Make sure projected fits are on top
- rootOrder = container.keys()
- rootOrder.sort(key=lambda x: self.ORDER.index(type(x)))
-
- # Now, we take our created dictionary and start adding stuff to our tree
- for thing in rootOrder:
- # This block simply directs which parent we are adding to (root or projected fit)
- if thing == self.stuff:
- parent = root
- else: # projected fit
- icon = self.imageList.Add(BitmapLoader.getBitmap("ship_small", "gui"))
- child = self.affectedBy.AppendItem(root, "{} ({})".format(thing.name, thing.ship.item.name), icon)
- parent = child
-
- attributes = container[thing]
- attrOrder = sorted(attributes.keys(), key=self.sortAttrDisplayName)
-
- for attrName in attrOrder:
- attrInfo = self.stuff.item.attributes.get(attrName)
- displayName = attrInfo.displayName if attrInfo and attrInfo.displayName != "" else attrName
-
- if attrInfo:
- if attrInfo.icon is not None:
- iconFile = attrInfo.icon.iconFile
- icon = BitmapLoader.getBitmap(iconFile, "icons")
- if icon is None:
- icon = BitmapLoader.getBitmap("transparent16x16", "gui")
- attrIcon = self.imageList.Add(icon)
- else:
- attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons"))
- else:
- attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons"))
-
- if self.showRealNames:
- display = attrName
- saved = displayName
- else:
- display = displayName
- saved = attrName
-
- # this is the attribute node
- child = self.affectedBy.AppendItem(parent, display, attrIcon)
- self.affectedBy.SetPyData(child, saved)
- self.treeItems.append(child)
-
- items = attributes[attrName]
- items.sort(key=lambda x: self.ORDER.index(x[0]))
- for itemInfo in items:
- afflictorType, afflictor, item, attrModifier, attrAmount, projected = itemInfo
-
- if afflictorType == Ship:
- itemIcon = self.imageList.Add(BitmapLoader.getBitmap("ship_small", "gui"))
- elif item.icon:
- bitmap = BitmapLoader.getBitmap(item.icon.iconFile, "icons")
- itemIcon = self.imageList.Add(bitmap) if bitmap else -1
- else:
- itemIcon = -1
-
- displayStr = item.name
-
- if projected:
- displayStr += " (projected)"
-
- penalized = ""
- if '*' in attrModifier:
- if 's' in attrModifier:
- penalized += "(penalized)"
- if 'r' in attrModifier:
- penalized += "(resisted)"
- attrModifier = "*"
-
- # this is the Module node, the attribute will be attached to this
- display = "%s %s %.2f %s" % (displayStr, attrModifier, attrAmount, penalized)
- treeItem = self.affectedBy.AppendItem(child, display, itemIcon)
- self.affectedBy.SetPyData(treeItem, afflictor)
-
- def buildModuleView(self, root):
- """
- We first build a usable dictionary of items. The key is either a fit
- if the afflictions stem from a projected fit, or self.stuff if they
- are local afflictions (everything else, even gang boosts at this time)
- The value of this is yet another dictionary in the following format:
-
- "Module Name": [
- class of affliction,
- set of afflictors (such as 2 of the same module),
- info on affliction (attribute name, modifier, and modification amount),
- item that will be used to determine icon (required due to GH issue #335)
- whether this affliction is actually used (unlearned skills are not used)
- ]
- """
-
- attributes = self.stuff.itemModifiedAttributes if self.item == self.stuff.item else self.stuff.chargeModifiedAttributes
- container = {}
- for attrName in attributes.iterAfflictions():
- # if value is 0 or there has been no change from original to modified, return
- if attributes[attrName] == (attributes.getOriginal(attrName, 0)):
- continue
-
- for fit, afflictors in attributes.getAfflictions(attrName).iteritems():
- for afflictor, modifier, amount, used in afflictors:
- if not used or getattr(afflictor, 'item', None) is None:
- continue
-
- if fit.ID != self.activeFit:
- # affliction fit does not match our fit
- if fit not in container:
- container[fit] = {}
- items = container[fit]
- else:
- # local afflictions
- if self.stuff not in container:
- container[self.stuff] = {}
- items = container[self.stuff]
-
- if afflictor == self.stuff and getattr(afflictor, 'charge', None):
- # we are showing a charges modifications, see #335
- item = afflictor.charge
- else:
- item = afflictor.item
-
- # items hold our module: info mappings
- if item.name not in items:
- items[item.name] = [type(afflictor), set(), [], item, getattr(afflictor, "projected", False)]
-
- info = items[item.name]
- info[1].add(afflictor)
- # If info[1] > 1, there are two separate modules working.
- # Check to make sure we only include the modifier once
- # See GH issue 154
- if len(info[1]) > 1 and (attrName, modifier, amount) in info[2]:
- continue
- info[2].append((attrName, modifier, amount))
-
- # Make sure projected fits are on top
- rootOrder = container.keys()
- rootOrder.sort(key=lambda x: self.ORDER.index(type(x)))
-
- # Now, we take our created dictionary and start adding stuff to our tree
- for thing in rootOrder:
- # This block simply directs which parent we are adding to (root or projected fit)
- if thing == self.stuff:
- parent = root
- else: # projected fit
- icon = self.imageList.Add(BitmapLoader.getBitmap("ship_small", "gui"))
- child = self.affectedBy.AppendItem(root, "{} ({})".format(thing.name, thing.ship.item.name), icon)
- parent = child
-
- items = container[thing]
- order = items.keys()
- order.sort(key=lambda x: (self.ORDER.index(items[x][0]), x))
-
- for itemName in order:
- info = items[itemName]
- afflictorType, afflictors, attrData, item, projected = info
- counter = len(afflictors)
- if afflictorType == Ship:
- itemIcon = self.imageList.Add(BitmapLoader.getBitmap("ship_small", "gui"))
- elif item.icon:
- bitmap = BitmapLoader.getBitmap(item.icon.iconFile, "icons")
- itemIcon = self.imageList.Add(bitmap) if bitmap else -1
- else:
- itemIcon = -1
-
- displayStr = itemName
-
- if counter > 1:
- displayStr += " x {}".format(counter)
-
- if projected:
- displayStr += " (projected)"
-
- # this is the Module node, the attribute will be attached to this
- child = self.affectedBy.AppendItem(parent, displayStr, itemIcon)
- self.affectedBy.SetPyData(child, afflictors.pop())
-
- if counter > 0:
- attributes = []
- for attrName, attrModifier, attrAmount in attrData:
- attrInfo = self.stuff.item.attributes.get(attrName)
- displayName = attrInfo.displayName if attrInfo else ""
-
- if attrInfo:
- if attrInfo.icon is not None:
- iconFile = attrInfo.icon.iconFile
- icon = BitmapLoader.getBitmap(iconFile, "icons")
- if icon is None:
- icon = BitmapLoader.getBitmap("transparent16x16", "gui")
-
- attrIcon = self.imageList.Add(icon)
- else:
- attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons"))
- else:
- attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons"))
-
- penalized = ""
- if '*' in attrModifier:
- if 's' in attrModifier:
- penalized += "(penalized)"
- if 'r' in attrModifier:
- penalized += "(resisted)"
- attrModifier = "*"
-
- attributes.append((attrName, (displayName if displayName != "" else attrName), attrModifier,
- attrAmount, penalized, attrIcon))
-
- attrSorted = sorted(attributes, key=lambda attribName: attribName[0])
- for attr in attrSorted:
- attrName, displayName, attrModifier, attrAmount, penalized, attrIcon = attr
-
- 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
- )
- else:
- 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)
- self.affectedBy.SetPyData(treeitem, saved)
- self.treeItems.append(treeitem)
-
-
-class ItemProperties(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)
- mainSizer.Add(self.paramList, 1, wx.ALL | wx.EXPAND, 0)
- self.SetSizer(mainSizer)
-
- self.toggleView = 1
- self.stuff = stuff
- self.item = item
- self.attrInfo = {}
- self.attrValues = {}
- self._fetchValues()
-
- self.m_staticline = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL)
- mainSizer.Add(self.m_staticline, 0, wx.EXPAND)
- bSizer = wx.BoxSizer(wx.HORIZONTAL)
-
- 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)
-
- mainSizer.Add(bSizer, 0, wx.ALIGN_RIGHT)
-
- self.PopulateList()
-
- def _fetchValues(self):
- if self.stuff is None:
- self.attrInfo.clear()
- self.attrValues.clear()
- self.attrInfo.update(self.item.attributes)
- self.attrValues.update(self.item.attributes)
- elif self.stuff.item == self.item:
- self.attrInfo.clear()
- self.attrValues.clear()
- self.attrInfo.update(self.stuff.item.attributes)
- self.attrValues.update(self.stuff.itemModifiedAttributes)
- elif self.stuff.charge == self.item:
- self.attrInfo.clear()
- self.attrValues.clear()
- self.attrInfo.update(self.stuff.charge.attributes)
- self.attrValues.update(self.stuff.chargeModifiedAttributes)
- # When item for stats window no longer exists, don't change anything
- else:
- return
-
- def PopulateList(self):
- self.paramList.InsertColumn(0, "Attribute")
- self.paramList.InsertColumn(1, "Current Value")
- self.paramList.SetColumnWidth(0, 110)
- self.paramList.SetColumnWidth(1, 1500)
- self.paramList.setResizeColumn(0)
-
- if self.stuff:
- names = dir(self.stuff)
- else:
- names = dir(self.item)
-
- names = [a for a in names if not (a.startswith('__') and a.endswith('__'))]
-
- idNameMap = {}
- idCount = 0
- for name in names:
- try:
- if self.stuff:
- attrName = name.title()
- value = getattr(self.stuff, name)
- else:
- attrName = name.title()
- value = getattr(self.item, name)
-
- index = self.paramList.InsertStringItem(sys.maxint, attrName)
- # index = self.paramList.InsertImageStringItem(sys.maxint, attrName)
- idNameMap[idCount] = attrName
- self.paramList.SetItemData(index, idCount)
- idCount += 1
-
- valueUnit = str(value)
-
- self.paramList.SetStringItem(index, 1, valueUnit)
- except:
- # TODO: Add logging to this.
- # We couldn't get a property for some reason. Skip it for now.
- continue
-
- self.paramList.SortItems(lambda id1, id2: cmp(idNameMap[id1], idNameMap[id2]))
- self.paramList.RefreshRows()
- self.totalAttrsLabel.SetLabel("%d attributes. " % idCount)
- self.Layout()
diff --git a/gui/statsPane.py b/gui/statsPane.py
index ce09e2e62..c186c4531 100644
--- a/gui/statsPane.py
+++ b/gui/statsPane.py
@@ -143,3 +143,9 @@ class StatsPane(wx.Panel):
event.Skip()
return handler
+
+ @staticmethod
+ def applyBinding(self, contentPanel):
+ pyfalog.debug("Attempt applyBinding to children of {0}", contentPanel.viewName)
+ for child in contentPanel.GetChildren():
+ child.Bind(wx.EVT_RIGHT_DOWN, self.contextHandler(contentPanel))
diff --git a/tox.ini b/tox.ini
index ab540fa30..7c73fef70 100644
--- a/tox.ini
+++ b/tox.ini
@@ -13,4 +13,4 @@ commands = py.test -vv --cov Pyfa tests/
[testenv:pep8]
deps = flake8
# TODO: Remove E731 and convert lambdas to defs
-commands = flake8 --exclude=.svn,CVS,.bzr,.hg,.git,__pycache__,venv,tests,.tox,build,dist,__init__.py,floatspin.py --ignore=E121,E126,E127,E128,E203,E731 service gui eos utils config.py pyfa.py --max-line-length=165
+commands = flake8 --exclude=.svn,CVS,.bzr,.hg,.git,__pycache__,venv,tests,.tox,build,dist,__init__.py,floatspin.py --ignore=E121,E126,E127,E128,E203,E731,F401 service gui eos utils config.py pyfa.py --max-line-length=165