diff --git a/eos/db/__init__.py b/eos/db/__init__.py
index 807ce950f..7aef55fd7 100644
--- a/eos/db/__init__.py
+++ b/eos/db/__init__.py
@@ -68,14 +68,8 @@ from eos.db.gamedata import *
from eos.db.saveddata import *
#Import queries
-from eos.db.gamedata.queries import getItem, searchItems, getVariations, getItemsByCategory, directAttributeRequest, \
- getMarketGroup, getGroup, getCategory, getAttributeInfo, getMetaData, getMetaGroup
-from eos.db.saveddata.queries import getUser, getCharacter, getFit, getFitsWithShip, countFitsWithShip, searchFits, \
- getCharacterList, getPrice, getDamagePatternList, getDamagePattern, \
- getFitList, getFleetList, getFleet, save, remove, commit, add, \
- getCharactersForUser, getMiscData, getSquadsIDsWithFitID, getWing, \
- getSquad, getBoosterFits, getProjectedFits, getTargetResistsList, getTargetResists,\
- clearPrices, countAllFits, getCrestCharacters, getCrestCharacter
+from eos.db.gamedata.queries import *
+from eos.db.saveddata.queries import *
#If using in memory saveddata, you'll want to reflect it so the data structure is good.
if config.saveddata_connectionstring == "sqlite:///:memory:":
diff --git a/eos/db/saveddata/__init__.py b/eos/db/saveddata/__init__.py
index c0677230d..683fb499d 100644
--- a/eos/db/saveddata/__init__.py
+++ b/eos/db/saveddata/__init__.py
@@ -1,3 +1,18 @@
-__all__ = ["character", "fit", "module", "user", "crest", "skill", "price",
- "booster", "drone", "implant", "fleet", "damagePattern",
- "miscData", "targetResists"]
+__all__ = [
+ "character",
+ "fit",
+ "module",
+ "user",
+ "skill",
+ "price",
+ "booster",
+ "drone",
+ "implant",
+ "fleet",
+ "damagePattern",
+ "miscData",
+ "targetResists",
+ "override",
+ "crest"
+]
+
diff --git a/eos/db/saveddata/override.py b/eos/db/saveddata/override.py
new file mode 100644
index 000000000..2d5d74a47
--- /dev/null
+++ b/eos/db/saveddata/override.py
@@ -0,0 +1,31 @@
+#===============================================================================
+# Copyright (C) 2010 Diego Duclos
+#
+# This file is part of eos.
+#
+# eos is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# eos is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with eos. If not, see .
+#===============================================================================
+
+from sqlalchemy import Table, Column, Integer, Float
+from sqlalchemy.orm import mapper
+
+from eos.db import saveddata_meta
+from eos.types import Override
+
+overrides_table = Table("overrides", saveddata_meta,
+ Column("itemID", Integer, primary_key=True, index = True),
+ Column("attrID", Integer, primary_key=True, index = True),
+ Column("value", Float, nullable = False))
+
+mapper(Override, overrides_table)
diff --git a/eos/db/saveddata/queries.py b/eos/db/saveddata/queries.py
index 5c58a8501..a305994cd 100644
--- a/eos/db/saveddata/queries.py
+++ b/eos/db/saveddata/queries.py
@@ -19,7 +19,8 @@
from eos.db.util import processEager, processWhere
from eos.db import saveddata_session, sd_lock
-from eos.types import User, Character, Fit, Price, DamagePattern, Fleet, MiscData, Wing, Squad, TargetResists, CrestChar
+
+from eos.types import User, Character, Fit, Price, DamagePattern, Fleet, MiscData, Wing, Squad, TargetResists, Override, CrestChar
from eos.db.saveddata.fleet import squadmembers_table
from eos.db.saveddata.fit import projectedFits_table
from sqlalchemy.sql import and_
@@ -182,7 +183,7 @@ def getFit(lookfor, eager=None):
else:
eager = processEager(eager)
with sd_lock:
- fit = saveddata_session.query(Fit).options(*eager).filter(Fit.ID == fitID).first()
+ fit = saveddata_session.query(Fit).options(*eager).filter(Fit.ID == lookfor).first()
else:
raise TypeError("Need integer as argument")
@@ -440,6 +441,21 @@ def getCrestCharacter(lookfor, eager=None):
raise TypeError("Need integer or string as argument")
return character
+def getOverrides(itemID, eager=None):
+ if isinstance(itemID, int):
+ return saveddata_session.query(Override).filter(Override.itemID == itemID).all()
+ else:
+ raise TypeError("Need integer as argument")
+
+def clearOverrides():
+ with sd_lock:
+ deleted_rows = saveddata_session.query(Override).delete()
+ commit()
+ return deleted_rows
+
+def getAllOverrides(eager=None):
+ return saveddata_session.query(Override).all()
+
def removeInvalid(fits):
invalids = [f for f in fits if f.isInvalid]
diff --git a/eos/gamedata.py b/eos/gamedata.py
index d5691da46..3e7c213eb 100644
--- a/eos/gamedata.py
+++ b/eos/gamedata.py
@@ -24,6 +24,7 @@ from sqlalchemy.orm import reconstructor
from eqBase import EqBase
import traceback
+import eos.db
try:
from collections import OrderedDict
@@ -168,7 +169,6 @@ class Item(EqBase):
info = getattr(cls, "MOVE_ATTR_INFO", None)
if info is None:
cls.MOVE_ATTR_INFO = info = []
- import eos.db
for id in cls.MOVE_ATTRS:
info.append(eos.db.getAttributeInfo(id))
@@ -191,6 +191,7 @@ class Item(EqBase):
self.__moved = False
self.__offensive = None
self.__assistive = None
+ self.__overrides = None
@property
def attributes(self):
@@ -210,6 +211,32 @@ class Item(EqBase):
return False
+ @property
+ def overrides(self):
+ if self.__overrides is None:
+ self.__overrides = {}
+ overrides = eos.db.getOverrides(self.ID)
+ for x in overrides:
+ if x.attr.name in self.__attributes:
+ self.__overrides[x.attr.name] = x
+
+ return self.__overrides
+
+ def setOverride(self, attr, value):
+ from eos.saveddata.override import Override
+ if attr.name in self.__overrides:
+ override = self.__overrides.get(attr.name)
+ override.value = value
+ else:
+ override = Override(self, attr, value)
+ self.__overrides[attr.name] = override
+ eos.db.save(override)
+
+ def deleteOverride(self, attr):
+ override = self.__overrides.pop(attr.name, None)
+ eos.db.saveddata_session.delete(override)
+ eos.db.commit()
+
@property
def requiredSkills(self):
if self.__requiredSkills is None:
@@ -345,6 +372,12 @@ class Item(EqBase):
return False
+ def __repr__(self):
+ return "Item(ID={}, name={}) at {}".format(
+ self.ID, self.name, hex(id(self))
+ )
+
+
class MetaData(EqBase):
pass
diff --git a/eos/modifiedAttributeDict.py b/eos/modifiedAttributeDict.py
index de63420d1..f53384069 100644
--- a/eos/modifiedAttributeDict.py
+++ b/eos/modifiedAttributeDict.py
@@ -38,6 +38,9 @@ class ChargeAttrShortcut(object):
return None
class ModifiedAttributeDict(collections.MutableMapping):
+
+ OVERRIDES = False
+
class CalculationPlaceholder():
pass
@@ -51,6 +54,8 @@ class ModifiedAttributeDict(collections.MutableMapping):
self.__modified = {}
# Affected by entities
self.__affectedBy = {}
+ # Overrides
+ self.__overrides = {}
# Dictionaries for various value modification types
self.__forced = {}
self.__preAssigns = {}
@@ -79,6 +84,14 @@ class ModifiedAttributeDict(collections.MutableMapping):
self.__original = val
self.__modified.clear()
+ @property
+ def overrides(self):
+ return self.__overrides
+
+ @overrides.setter
+ def overrides(self, val):
+ self.__overrides = val
+
def __getitem__(self, key):
# Check if we have final calculated value
if key in self.__modified:
@@ -99,6 +112,8 @@ class ModifiedAttributeDict(collections.MutableMapping):
del self.__intermediary[key]
def getOriginal(self, key):
+ if self.OVERRIDES and key in self.__overrides:
+ return self.__overrides.get(key).value
val = self.__original.get(key)
if val is None:
return None
diff --git a/eos/saveddata/booster.py b/eos/saveddata/booster.py
index c0874fe94..9d0e98ae0 100644
--- a/eos/saveddata/booster.py
+++ b/eos/saveddata/booster.py
@@ -58,6 +58,7 @@ class Booster(HandledItem, ItemAttrShortcut):
self.__sideEffects = []
self.__itemModifiedAttributes = ModifiedAttributeDict()
self.__itemModifiedAttributes.original = self.__item.attributes
+ self.__itemModifiedAttributes.overrides = self.__item.overrides
self.__slot = self.__calculateSlot(self.__item)
for effect in self.__item.effects.itervalues():
diff --git a/eos/saveddata/cargo.py b/eos/saveddata/cargo.py
index 26509d525..676b7cebf 100644
--- a/eos/saveddata/cargo.py
+++ b/eos/saveddata/cargo.py
@@ -34,6 +34,7 @@ class Cargo(HandledItem, ItemAttrShortcut):
self.amount = 0
self.__itemModifiedAttributes = ModifiedAttributeDict()
self.__itemModifiedAttributes.original = item.attributes
+ self.__itemModifiedAttributes.overrides = item.overrides
@reconstructor
def init(self):
@@ -48,6 +49,7 @@ class Cargo(HandledItem, ItemAttrShortcut):
self.__itemModifiedAttributes = ModifiedAttributeDict()
self.__itemModifiedAttributes.original = self.__item.attributes
+ self.__itemModifiedAttributes.overrides = self.__item.overrides
@property
def itemModifiedAttributes(self):
diff --git a/eos/saveddata/drone.py b/eos/saveddata/drone.py
index 1a63d57a3..2fcb6a908 100644
--- a/eos/saveddata/drone.py
+++ b/eos/saveddata/drone.py
@@ -67,6 +67,7 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
self.__miningyield = None
self.__itemModifiedAttributes = ModifiedAttributeDict()
self.__itemModifiedAttributes.original = self.__item.attributes
+ self.__itemModifiedAttributes.overrides = self.__item.overrides
self.__chargeModifiedAttributes = ModifiedAttributeDict()
chargeID = self.getModifiedItemAttr("entityMissileTypeID")
@@ -74,6 +75,7 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
charge = eos.db.getItem(int(chargeID))
self.__charge = charge
self.__chargeModifiedAttributes.original = charge.attributes
+ self.__chargeModifiedAttributes.overrides = charge.overrides
@property
def itemModifiedAttributes(self):
diff --git a/eos/saveddata/implant.py b/eos/saveddata/implant.py
index 64670d769..b5be77986 100644
--- a/eos/saveddata/implant.py
+++ b/eos/saveddata/implant.py
@@ -56,6 +56,7 @@ class Implant(HandledItem, ItemAttrShortcut):
""" Build object. Assumes proper and valid item already set """
self.__itemModifiedAttributes = ModifiedAttributeDict()
self.__itemModifiedAttributes.original = self.__item.attributes
+ self.__itemModifiedAttributes.overrides = self.__item.overrides
self.__slot = self.__calculateSlot(self.__item)
@property
diff --git a/eos/saveddata/mode.py b/eos/saveddata/mode.py
index 3a344d74d..91fbaf6eb 100644
--- a/eos/saveddata/mode.py
+++ b/eos/saveddata/mode.py
@@ -30,6 +30,7 @@ class Mode(ItemAttrShortcut, HandledItem):
self.__item = item
self.__itemModifiedAttributes = ModifiedAttributeDict()
self.__itemModifiedAttributes.original = self.item.attributes
+ self.__itemModifiedAttributes.overrides = self.item.overrides
@property
def item(self):
diff --git a/eos/saveddata/module.py b/eos/saveddata/module.py
index e5468553a..5fbb75e1c 100644
--- a/eos/saveddata/module.py
+++ b/eos/saveddata/module.py
@@ -113,10 +113,13 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
if self.__item:
self.__itemModifiedAttributes.original = self.__item.attributes
+ self.__itemModifiedAttributes.overrides = self.__item.overrides
self.__hardpoint = self.__calculateHardpoint(self.__item)
self.__slot = self.__calculateSlot(self.__item)
if self.__charge:
self.__chargeModifiedAttributes.original = self.__charge.attributes
+ self.__chargeModifiedAttributes.overrides = self.__charge.overrides
+
@classmethod
def buildEmpty(cls, slot):
@@ -283,9 +286,11 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
if charge is not None:
self.chargeID = charge.ID
self.__chargeModifiedAttributes.original = charge.attributes
+ self.__chargeModifiedAttributes.overrides = charge.overrides
else:
self.chargeID = None
self.__chargeModifiedAttributes.original = None
+ self.__chargeModifiedAttributes.overrides = {}
self.__itemModifiedAttributes.clear()
diff --git a/eos/saveddata/override.py b/eos/saveddata/override.py
new file mode 100644
index 000000000..b33875e89
--- /dev/null
+++ b/eos/saveddata/override.py
@@ -0,0 +1,59 @@
+#===============================================================================
+# Copyright (C) 2015 Ryan Holmes
+#
+# This file is part of eos.
+#
+# eos is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# eos is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with eos. If not, see .
+#===============================================================================
+
+from eos.eqBase import EqBase
+from sqlalchemy.orm import validates, reconstructor
+import eos.db
+import logging
+
+logger = logging.getLogger(__name__)
+
+class Override(EqBase):
+
+ def __init__(self, item, attr, value):
+ self.itemID = item.ID
+ self.__item = item
+ self.attrID = attr.ID
+ self.__attr = attr
+ self.value = value
+
+ @reconstructor
+ def init(self):
+ self.__attr = None
+ self.__item = None
+
+ if self.attrID:
+ self.__attr = eos.db.getAttributeInfo(self.attrID)
+ if self.__attr is None:
+ logger.error("Attribute (id: %d) does not exist", self.attrID)
+ return
+
+ if self.itemID:
+ self.__item = eos.db.getItem(self.itemID)
+ if self.__item is None:
+ logger.error("Item (id: %d) does not exist", self.itemID)
+ return
+
+ @property
+ def attr(self):
+ return self.__attr
+
+ @property
+ def item(self):
+ return self.__item
diff --git a/eos/saveddata/ship.py b/eos/saveddata/ship.py
index daa5e99ec..0a0e13c45 100644
--- a/eos/saveddata/ship.py
+++ b/eos/saveddata/ship.py
@@ -51,6 +51,7 @@ class Ship(ItemAttrShortcut, HandledItem):
self.__itemModifiedAttributes = ModifiedAttributeDict()
self.__itemModifiedAttributes.original = dict(self.item.attributes)
self.__itemModifiedAttributes.original.update(self.EXTRA_ATTRIBUTES)
+ self.__itemModifiedAttributes.overrides = self.item.overrides
self.commandBonus = 0
diff --git a/eos/types.py b/eos/types.py
index 7bd2d8c7e..c1cafd1f5 100644
--- a/eos/types.py
+++ b/eos/types.py
@@ -36,4 +36,5 @@ from eos.saveddata.fit import Fit
from eos.saveddata.mode import Mode
from eos.saveddata.fleet import Fleet, Wing, Squad
from eos.saveddata.miscData import MiscData
+from eos.saveddata.override import Override
import eos.db
diff --git a/gui/PFSearchBox.py b/gui/PFSearchBox.py
index 88c67617b..7fff91e9b 100644
--- a/gui/PFSearchBox.py
+++ b/gui/PFSearchBox.py
@@ -11,7 +11,7 @@ TextTyped, EVT_TEXT = wx.lib.newevent.NewEvent()
class PFSearchBox(wx.Window):
def __init__(self, parent, id = wx.ID_ANY, value = "", pos = wx.DefaultPosition, size = wx.Size(-1,24), style = 0):
- wx.Window.__init__(self, parent, id, pos, size, style = 0)
+ wx.Window.__init__(self, parent, id, pos, size, style = style)
self.isSearchButtonVisible = False
self.isCancelButtonVisible = False
diff --git a/gui/characterEditor.py b/gui/characterEditor.py
index 92b3b760c..0cebb5101 100644
--- a/gui/characterEditor.py
+++ b/gui/characterEditor.py
@@ -35,9 +35,6 @@ class CharacterEditor(wx.Frame):
size=wx.Size(641, 600), style=wx.DEFAULT_FRAME_STYLE|wx.FRAME_FLOAT_ON_PARENT|wx.TAB_TRAVERSAL)
i = wx.IconFromBitmap(BitmapLoader.getBitmap("character_small", "gui"))
-
- self.mainFrame = parent
-
self.SetIcon(i)
self.disableWin= wx.WindowDisabler(self)
diff --git a/gui/mainFrame.py b/gui/mainFrame.py
index 6b8a18237..17e39af71 100644
--- a/gui/mainFrame.py
+++ b/gui/mainFrame.py
@@ -55,7 +55,12 @@ from gui.copySelectDialog import CopySelectDialog
from gui.utils.clipboard import toClipboard, fromClipboard
from gui.fleetBrowser import FleetBrowser
from gui.updateDialog import UpdateDialog
+from gui.propertyEditor import AttributeEditor
from gui.builtinViews import *
+
+# import this to access override setting
+from eos.modifiedAttributeDict import ModifiedAttributeDict
+
from time import gmtime, strftime
from service.crest import CrestModes
@@ -340,6 +345,10 @@ class MainFrame(wx.Frame):
dlg=CharacterEditor(self)
dlg.Show()
+ def showAttrEditor(self, event):
+ dlg=AttributeEditor(self)
+ dlg.Show()
+
def showTargetResistsEditor(self, event):
dlg=ResistsEditorDlg(self)
dlg.ShowModal()
@@ -427,6 +436,7 @@ class MainFrame(wx.Frame):
self.Bind(wx.EVT_MENU, self.saveCharAs, id = menuBar.saveCharAsId)
# Save current character
self.Bind(wx.EVT_MENU, self.revertChar, id = menuBar.revertCharId)
+
# Browse fittings
self.Bind(wx.EVT_MENU, self.eveFittings, id = menuBar.eveFittingsId)
# Export to EVE
@@ -434,6 +444,11 @@ class MainFrame(wx.Frame):
# Login to EVE
self.Bind(wx.EVT_MENU, self.ssoLogin, id = menuBar.ssoLoginId)
+ # Open attribute editor
+ self.Bind(wx.EVT_MENU, self.showAttrEditor, id = menuBar.attrEditorId)
+ # Toggle Overrides
+ self.Bind(wx.EVT_MENU, self.toggleOverrides, id = menuBar.toggleOverridesId)
+
#Clipboard exports
self.Bind(wx.EVT_MENU, self.exportToClipboard, id=wx.ID_COPY)
@@ -534,6 +549,12 @@ class MainFrame(wx.Frame):
dlg=ExportToEve(self)
dlg.Show()
+ def toggleOverrides(self, event):
+ ModifiedAttributeDict.OVERRIDES = not ModifiedAttributeDict.OVERRIDES
+ wx.PostEvent(self, GE.FitChanged(fitID=self.getActiveFit()))
+ menu = self.GetMenuBar()
+ menu.SetLabel(menu.toggleOverridesId, "Turn Overrides Off" if ModifiedAttributeDict.OVERRIDES else "Turn Overrides On")
+
def saveChar(self, event):
sChr = service.Character.getInstance()
charID = self.charSelection.getActiveCharacter()
diff --git a/gui/mainMenuBar.py b/gui/mainMenuBar.py
index 95772fdbc..cf8f85198 100644
--- a/gui/mainMenuBar.py
+++ b/gui/mainMenuBar.py
@@ -47,6 +47,8 @@ class MainMenuBar(wx.MenuBar):
self.eveFittingsId = wx.NewId()
self.exportToEveId = wx.NewId()
self.ssoLoginId = wx.NewId()
+ self.attrEditorId = wx.NewId()
+ self.toggleOverridesId = wx.NewId()
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
@@ -87,6 +89,9 @@ class MainMenuBar(wx.MenuBar):
editMenu.Append(self.saveCharId, "Save Character")
editMenu.Append(self.saveCharAsId, "Save Character As...")
editMenu.Append(self.revertCharId, "Revert Character")
+ editMenu.AppendSeparator()
+ editMenu.Append(self.toggleOverridesId, "Turn Overrides On")
+
# Character menu
windowMenu = wx.Menu()
self.Append(windowMenu, "&Window")
@@ -125,6 +130,10 @@ class MainMenuBar(wx.MenuBar):
self.Enable(self.eveFittingsId, False)
self.Enable(self.exportToEveId, False)
+ attrItem = wx.MenuItem(windowMenu, self.attrEditorId, "Attribute Overrides\tCTRL+A")
+ attrItem.SetBitmap(BitmapLoader.getBitmap("fit_rename_small", "gui"))
+ windowMenu.AppendItem(attrItem)
+
# Help menu
helpMenu = wx.Menu()
self.Append(helpMenu, "&Help")
diff --git a/gui/marketBrowser.py b/gui/marketBrowser.py
index 53f704c37..14dea666c 100644
--- a/gui/marketBrowser.py
+++ b/gui/marketBrowser.py
@@ -103,8 +103,8 @@ class MarketBrowser(wx.Panel):
self.marketView.jump(item)
class SearchBox(SBox.PFSearchBox):
- def __init__(self, parent):
- SBox.PFSearchBox.__init__(self, parent)
+ def __init__(self, parent, **kwargs):
+ SBox.PFSearchBox.__init__(self, parent, **kwargs)
cancelBitmap = BitmapLoader.getBitmap("fit_delete_small","gui")
searchBitmap = BitmapLoader.getBitmap("fsearch_small","gui")
self.SetSearchBitmap(searchBitmap)
diff --git a/gui/propertyEditor.py b/gui/propertyEditor.py
new file mode 100644
index 000000000..406007eb3
--- /dev/null
+++ b/gui/propertyEditor.py
@@ -0,0 +1,264 @@
+import wx
+import wx.propgrid as wxpg
+
+import gui.PFSearchBox as SBox
+from gui.marketBrowser import SearchBox
+import gui.display as d
+import gui.globalEvents as GE
+from gui.bitmapLoader import BitmapLoader
+import service
+import csv
+import eos.db
+
+import logging
+
+logger = logging.getLogger(__name__)
+
+class AttributeEditor( wx.Frame ):
+
+ def __init__( self, parent ):
+ wx.Frame.__init__(self, parent, wx.ID_ANY, title="Attribute Editor", pos=wx.DefaultPosition,
+ size=wx.Size(650, 600), style=wx.DEFAULT_FRAME_STYLE|wx.FRAME_FLOAT_ON_PARENT|wx.TAB_TRAVERSAL)
+
+ i = wx.IconFromBitmap(BitmapLoader.getBitmap("fit_rename_small", "gui"))
+ self.SetIcon(i)
+
+ self.mainFrame = parent
+
+ menubar = wx.MenuBar()
+ fileMenu = wx.Menu()
+ fileImport = fileMenu.Append(wx.ID_ANY, 'Import', 'Import overrides')
+ fileExport = fileMenu.Append(wx.ID_ANY, 'Export', 'Import overrides')
+ fileClear = fileMenu.Append(wx.ID_ANY, 'Clear All', 'Clear all overrides')
+
+ menubar.Append(fileMenu, '&File')
+ self.SetMenuBar(menubar)
+
+ self.Bind(wx.EVT_MENU, self.OnImport, fileImport)
+ self.Bind(wx.EVT_MENU, self.OnExport, fileExport)
+ self.Bind(wx.EVT_MENU, self.OnClear, fileClear)
+
+
+ i = wx.IconFromBitmap(BitmapLoader.getBitmap("fit_rename_small", "gui"))
+ self.SetIcon(i)
+
+ self.mainFrame = parent
+ self.panel = panel = wx.Panel(self, wx.ID_ANY)
+
+ mainSizer = wx.BoxSizer(wx.HORIZONTAL)
+
+ leftSizer = wx.BoxSizer(wx.VERTICAL)
+ leftPanel = wx.Panel(panel, wx.ID_ANY, style=wx.DOUBLE_BORDER if 'wxMSW' in wx.PlatformInfo else wx.SIMPLE_BORDER)
+
+ self.searchBox = SearchBox(leftPanel)
+ self.itemView = ItemView(leftPanel)
+
+ leftSizer.Add(self.searchBox, 0, wx.EXPAND)
+ leftSizer.Add(self.itemView, 1, wx.EXPAND)
+
+ leftPanel.SetSizer(leftSizer)
+ mainSizer.Add(leftPanel, 1, wx.ALL | wx.EXPAND, 5)
+
+ rightSizer = wx.BoxSizer(wx.VERTICAL)
+ self.btnRemoveOverrides = wx.Button( panel, wx.ID_ANY, u"Remove Overides for Item", wx.DefaultPosition, wx.DefaultSize, 0 )
+ self.pg = AttributeGrid(panel)
+ rightSizer.Add(self.pg, 1, wx.ALL|wx.EXPAND, 5)
+ rightSizer.Add(self.btnRemoveOverrides, 0, wx.ALL | wx.EXPAND, 5 )
+ self.btnRemoveOverrides.Bind(wx.EVT_BUTTON, self.pg.removeOverrides)
+ self.btnRemoveOverrides.Enable(False)
+
+ mainSizer.Add(rightSizer, 1, wx.EXPAND)
+
+ panel.SetSizer(mainSizer)
+ mainSizer.SetSizeHints(panel)
+
+ sizer = wx.BoxSizer(wx.VERTICAL)
+ sizer.Add(panel, 1, wx.EXPAND)
+ self.SetSizer(sizer)
+ self.SetAutoLayout(True)
+
+ self.Bind(wx.EVT_CLOSE, self.OnClose)
+
+ def OnClose(self, event):
+ fitID = self.mainFrame.getActiveFit()
+ if fitID is not None:
+ wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
+ self.Destroy()
+
+ def OnImport(self, event):
+ dlg = wx.FileDialog(self, "Import pyfa override file",
+ wildcard = "pyfa override file (*.csv)|*.csv",
+ style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
+ if (dlg.ShowModal() == wx.ID_OK):
+ path = dlg.GetPath()
+ with open(path, 'rb') as csvfile:
+ spamreader = csv.reader(csvfile)
+ for row in spamreader:
+ itemID, attrID, value = row
+ item = eos.db.getItem(int(itemID))
+ attr = eos.db.getAttributeInfo(int(attrID))
+ item.setOverride(attr, float(value))
+ self.itemView.updateItems(True)
+
+ def OnExport(self, event):
+ sMkt = service.Market.getInstance()
+ items = sMkt.getItemsWithOverrides()
+ defaultFile = "pyfa_overrides.csv"
+
+ dlg = wx.FileDialog(self, "Save Overrides As...",
+ wildcard = "pyfa overrides (*.csv)|*.csv",
+ style = wx.FD_SAVE,
+ defaultFile=defaultFile)
+
+ if dlg.ShowModal() == wx.ID_OK:
+ path = dlg.GetPath()
+ with open(path, 'wb') as csvfile:
+ writer = csv.writer(csvfile)
+ for item in items:
+ for key, override in item.overrides.iteritems():
+ writer.writerow([item.ID, override.attrID, override.value])
+
+ def OnClear(self, event):
+ dlg = wx.MessageDialog(self,
+ "Are you sure you want to delete all overrides?",
+ "Confirm Delete", wx.YES | wx.NO | wx.ICON_EXCLAMATION)
+
+ if dlg.ShowModal() == wx.ID_YES:
+ sMkt = service.Market.getInstance()
+ items = sMkt.getItemsWithOverrides()
+ # We can't just delete overrides, as loaded items will still have
+ # them assigned. Deleting them from the database won't propagate
+ # them due to the eve/user database disconnect. We must loop through
+ # all items that have overrides and remove them
+ for item in items:
+ for _, x in item.overrides.items():
+ item.deleteOverride(x.attr)
+ self.itemView.updateItems(True)
+ self.pg.Clear()
+
+# This is literally a stripped down version of the market.
+class ItemView(d.Display):
+ DEFAULT_COLS = ["Base Icon",
+ "Base Name",
+ "attr:power,,,True",
+ "attr:cpu,,,True"]
+
+ def __init__(self, parent):
+ d.Display.__init__(self, parent)
+ sMkt = service.Market.getInstance()
+
+ self.things = sMkt.getItemsWithOverrides()
+ self.items = self.things
+
+ self.searchBox = parent.Parent.Parent.searchBox
+ # Bind search actions
+ self.searchBox.Bind(SBox.EVT_TEXT_ENTER, self.scheduleSearch)
+ self.searchBox.Bind(SBox.EVT_SEARCH_BTN, self.scheduleSearch)
+ self.searchBox.Bind(SBox.EVT_CANCEL_BTN, self.clearSearch)
+ self.searchBox.Bind(SBox.EVT_TEXT, self.scheduleSearch)
+
+ self.update(self.items)
+
+ def clearSearch(self, event=None):
+ if event:
+ self.searchBox.Clear()
+ self.items = self.things
+ self.update(self.items)
+
+ def updateItems(self, updateDisplay=False):
+ sMkt = service.Market.getInstance()
+ self.things = sMkt.getItemsWithOverrides()
+ self.items = self.things
+ if updateDisplay:
+ self.update(self.things)
+
+ def scheduleSearch(self, event=None):
+ sMkt = service.Market.getInstance()
+
+ search = self.searchBox.GetLineText(0)
+ # Make sure we do not count wildcard as search symbol
+ realsearch = search.replace("*", "")
+ # Show nothing if query is too short
+ if len(realsearch) < 3:
+ self.clearSearch()
+ return
+
+ sMkt.searchItems(search, self.populateSearch, False)
+
+ def populateSearch(self, items):
+ self.items = list(items)
+ self.update(items)
+
+
+class AttributeGrid(wxpg.PropertyGrid):
+
+ def __init__(self, parent):
+ wxpg.PropertyGrid.__init__(self, parent, style=wxpg.PG_HIDE_MARGIN|wxpg.PG_HIDE_CATEGORIES|wxpg.PG_BOLD_MODIFIED|wxpg.PG_TOOLTIPS)
+ self.SetExtraStyle(wxpg.PG_EX_HELP_AS_TOOLTIPS)
+
+ self.item = None
+
+ self.itemView = parent.Parent.itemView
+
+ self.btn = parent.Parent.btnRemoveOverrides
+
+ self.Bind( wxpg.EVT_PG_CHANGED, self.OnPropGridChange )
+ self.Bind( wxpg.EVT_PG_SELECTED, self.OnPropGridSelect )
+ self.Bind( wxpg.EVT_PG_RIGHT_CLICK, self.OnPropGridRightClick )
+
+ self.itemView.Bind(wx.EVT_LIST_ITEM_SELECTED, self.itemActivated)
+ self.itemView.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.itemActivated)
+
+ def itemActivated(self, event):
+ self.Clear()
+ self.btn.Enable(True)
+ sel = event.EventObject.GetFirstSelected()
+ self.item = item = self.itemView.items[sel]
+
+ for key in sorted(item.attributes.keys()):
+ override = item.overrides.get(key, None)
+ default = item.attributes[key].value
+ if override and override.value != default:
+ prop = wxpg.FloatProperty(key, value=override.value)
+ prop.SetModifiedStatus(True)
+ else:
+ prop = wxpg.FloatProperty(key, value=default)
+
+ prop.SetClientData(item.attributes[key]) # set this so that we may access it later
+ prop.SetHelpString("%s\n%s"%(item.attributes[key].displayName or key, "Default Value: %0.3f"%default))
+ self.Append(prop)
+
+ def removeOverrides(self, event):
+ if self.item is None:
+ return
+
+ for _, x in self.item.overrides.items():
+ self.item.deleteOverride(x.attr)
+ self.itemView.updateItems(True)
+ self.ClearModifiedStatus()
+ self.itemView.Select(self.itemView.GetFirstSelected(), on=False)
+ self.Clear()
+
+ def Clear(self):
+ self.item = None
+ self.btn.Enable(False)
+ wxpg.PropertyGrid.Clear(self)
+
+ def OnPropGridChange(self, event):
+ p = event.GetProperty()
+ attr = p.GetClientData()
+ if p.GetValue() == attr.value:
+ self.item.deleteOverride(attr)
+ p.SetModifiedStatus(False)
+ else:
+ self.item.setOverride(attr, p.GetValue())
+
+ self.itemView.updateItems()
+
+ logger.debug('%s changed to "%s"' % (p.GetName(), p.GetValueAsString()))
+
+ def OnPropGridSelect(self, event):
+ pass
+
+ def OnPropGridRightClick(self, event):
+ pass
diff --git a/service/market.py b/service/market.py
index e0a77b8b3..9abed103c 100644
--- a/service/market.py
+++ b/service/market.py
@@ -116,12 +116,15 @@ class SearchWorkerThread(threading.Thread):
while self.searchRequest is None:
cv.wait()
- request, callback = self.searchRequest
+ request, callback, filterOn = self.searchRequest
self.searchRequest = None
cv.release()
sMkt = Market.getInstance()
- # Rely on category data provided by eos as we don't hardcode them much in service
- filter = eos.types.Category.name.in_(sMkt.SEARCH_CATEGORIES)
+ if filterOn:
+ # Rely on category data provided by eos as we don't hardcode them much in service
+ filter = eos.types.Category.name.in_(sMkt.SEARCH_CATEGORIES)
+ else:
+ filter=None
results = eos.db.searchItems(request, where=filter,
join=(eos.types.Item.group, eos.types.Group.category),
eager=("icon", "group.category", "metaGroup", "metaGroup.parent"))
@@ -133,9 +136,9 @@ class SearchWorkerThread(threading.Thread):
items.add(item)
wx.CallAfter(callback, items)
- def scheduleSearch(self, text, callback):
+ def scheduleSearch(self, text, callback, filterOn=True):
self.cv.acquire()
- self.searchRequest = (text, callback)
+ self.searchRequest = (text, callback, filterOn)
self.cv.notify()
self.cv.release()
@@ -665,9 +668,16 @@ class Market():
ships.add(item)
return ships
- def searchItems(self, name, callback):
+ def searchItems(self, name, callback, filterOn=True):
"""Find items according to given text pattern"""
- self.searchWorkerThread.scheduleSearch(name, callback)
+ self.searchWorkerThread.scheduleSearch(name, callback, filterOn)
+
+ def getItemsWithOverrides(self):
+ overrides = eos.db.getAllOverrides()
+ items = set()
+ for x in overrides:
+ items.add(x.item)
+ return list(items)
def directAttrRequest(self, items, attribs):
try: