From f432b081497308789311aab8915e8a8c22134dea Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sat, 31 Oct 2015 16:53:20 -0400 Subject: [PATCH 01/12] Proof of concept. Need to save overrides in user database and create GUI for it. May be difficult to collect and apply considering the Item container is attached to the eve db and not the user. --- eos/gamedata.py | 9 +++++++++ overrides/__init__.py | 0 2 files changed, 9 insertions(+) create mode 100644 overrides/__init__.py diff --git a/eos/gamedata.py b/eos/gamedata.py index d5691da46..16eb0c4e9 100644 --- a/eos/gamedata.py +++ b/eos/gamedata.py @@ -183,6 +183,15 @@ class Item(EqBase): attr.info = info attr.value = val self.__attributes[info.name] = attr + try: + self.__overrides = __import__('overrides.' + str(self.ID), fromlist=True) + except ImportError: + self.__overrides = None + + if self.__overrides: + for key in dir(self.__overrides): + if key in self.__attributes: + self.__attributes[key] = getattr(self.__overrides, key) @reconstructor def init(self): diff --git a/overrides/__init__.py b/overrides/__init__.py new file mode 100644 index 000000000..e69de29bb From 56aba37758b648d1218c3ad57f36a1a58c888980 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sun, 1 Nov 2015 09:43:34 -0500 Subject: [PATCH 02/12] Fix PoC so that it actually works, and add a working test case for Web II (changing speedfactor to -80%) --- eos/gamedata.py | 2 +- overrides/527.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 overrides/527.py diff --git a/eos/gamedata.py b/eos/gamedata.py index 16eb0c4e9..ac984eedc 100644 --- a/eos/gamedata.py +++ b/eos/gamedata.py @@ -191,7 +191,7 @@ class Item(EqBase): if self.__overrides: for key in dir(self.__overrides): if key in self.__attributes: - self.__attributes[key] = getattr(self.__overrides, key) + self.__attributes[key].value = getattr(self.__overrides, key) @reconstructor def init(self): diff --git a/overrides/527.py b/overrides/527.py new file mode 100644 index 000000000..b1c82b796 --- /dev/null +++ b/overrides/527.py @@ -0,0 +1 @@ +speedFactor = -80.0 From 4eb61051c65f461f73f49303a06da2537f38e482 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sun, 1 Nov 2015 12:03:40 -0500 Subject: [PATCH 03/12] Add overrides to the modifiedAttributeDict. Still need to figure out how to switch between the original and the override --- eos/gamedata.py | 13 +++++++------ eos/modifiedAttributeDict.py | 10 ++++++++++ eos/saveddata/booster.py | 1 + eos/saveddata/cargo.py | 2 ++ eos/saveddata/drone.py | 2 ++ eos/saveddata/implant.py | 1 + eos/saveddata/mode.py | 1 + eos/saveddata/module.py | 5 +++++ eos/saveddata/ship.py | 1 + 9 files changed, 30 insertions(+), 6 deletions(-) diff --git a/eos/gamedata.py b/eos/gamedata.py index ac984eedc..35016fac3 100644 --- a/eos/gamedata.py +++ b/eos/gamedata.py @@ -184,14 +184,15 @@ class Item(EqBase): attr.value = val self.__attributes[info.name] = attr try: - self.__overrides = __import__('overrides.' + str(self.ID), fromlist=True) + mod = __import__('overrides.' + str(self.ID), fromlist=True) + self.overrides = {} + for key in dir(mod): + if key[:2] != "__": + self.overrides[key] = getattr(mod, key) except ImportError: - self.__overrides = None + self.overrides = {} + - if self.__overrides: - for key in dir(self.__overrides): - if key in self.__attributes: - self.__attributes[key].value = getattr(self.__overrides, key) @reconstructor def init(self): diff --git a/eos/modifiedAttributeDict.py b/eos/modifiedAttributeDict.py index de63420d1..76ef86007 100644 --- a/eos/modifiedAttributeDict.py +++ b/eos/modifiedAttributeDict.py @@ -51,6 +51,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 +81,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: 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/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 From b701acb2754a52acc51406abefc8334c8af5b607 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sun, 1 Nov 2015 13:02:29 -0500 Subject: [PATCH 04/12] Overrides work properly now. Currently toggled on/off by opening character editor, this will be taken over by a menu option --- eos/modifiedAttributeDict.py | 5 +++++ gui/characterEditor.py | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/eos/modifiedAttributeDict.py b/eos/modifiedAttributeDict.py index 76ef86007..c5dda0f71 100644 --- a/eos/modifiedAttributeDict.py +++ b/eos/modifiedAttributeDict.py @@ -38,6 +38,9 @@ class ChargeAttrShortcut(object): return None class ModifiedAttributeDict(collections.MutableMapping): + + OVERRIDES = True + class CalculationPlaceholder(): pass @@ -109,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) val = self.__original.get(key) if val is None: return None diff --git a/gui/characterEditor.py b/gui/characterEditor.py index 92b3b760c..4308f25ed 100644 --- a/gui/characterEditor.py +++ b/gui/characterEditor.py @@ -29,6 +29,8 @@ from gui.contextMenu import ContextMenu from wx.lib.buttons import GenBitmapButton import gui.globalEvents as GE +from eos.modifiedAttributeDict import ModifiedAttributeDict + class CharacterEditor(wx.Frame): def __init__(self, parent): wx.Frame.__init__ (self, parent, id=wx.ID_ANY, title=u"pyfa: Character Editor", pos=wx.DefaultPosition, @@ -36,6 +38,10 @@ class CharacterEditor(wx.Frame): i = wx.IconFromBitmap(BitmapLoader.getBitmap("character_small", "gui")) + print ModifiedAttributeDict.OVERRIDES + ModifiedAttributeDict.OVERRIDES = not ModifiedAttributeDict.OVERRIDES + print ModifiedAttributeDict.OVERRIDES + self.mainFrame = parent self.SetIcon(i) From 0e4cca6138c198670798039cc41f5c488b8e0a2d Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sun, 1 Nov 2015 15:00:09 -0500 Subject: [PATCH 05/12] Load overrides from database --- eos/db/__init__.py | 10 ++-------- eos/db/saveddata/__init__.py | 19 ++++++++++++++++--- eos/db/saveddata/queries.py | 8 +++++++- eos/gamedata.py | 25 +++++++++++++------------ eos/types.py | 1 + 5 files changed, 39 insertions(+), 24 deletions(-) diff --git a/eos/db/__init__.py b/eos/db/__init__.py index 8867d63c5..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 +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 31e71c01a..6bb3b2d86 100644 --- a/eos/db/saveddata/__init__.py +++ b/eos/db/saveddata/__init__.py @@ -1,3 +1,16 @@ -__all__ = ["character", "fit", "module", "user", "skill", "price", - "booster", "drone", "implant", "fleet", "damagePattern", - "miscData", "targetResists"] +__all__ = [ + "character", + "fit", + "module", + "user", + "skill", + "price", + "booster", + "drone", + "implant", + "fleet", + "damagePattern", + "miscData", + "targetResists", + "override" +] diff --git a/eos/db/saveddata/queries.py b/eos/db/saveddata/queries.py index b71f6b98a..3fbe55322 100644 --- a/eos/db/saveddata/queries.py +++ b/eos/db/saveddata/queries.py @@ -19,7 +19,7 @@ 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 +from eos.types import User, Character, Fit, Price, DamagePattern, Fleet, MiscData, Wing, Squad, TargetResists, Override from eos.db.saveddata.fleet import squadmembers_table from eos.db.saveddata.fit import projectedFits_table from sqlalchemy.sql import and_ @@ -416,6 +416,12 @@ def getProjectedFits(fitID): else: raise TypeError("Need integer as argument") +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 removeInvalid(fits): invalids = [f for f in fits if f.isInvalid] diff --git a/eos/gamedata.py b/eos/gamedata.py index 35016fac3..2dda6703a 100644 --- a/eos/gamedata.py +++ b/eos/gamedata.py @@ -24,7 +24,7 @@ from sqlalchemy.orm import reconstructor from eqBase import EqBase import traceback - +import eos.db try: from collections import OrderedDict except ImportError: @@ -168,7 +168,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)) @@ -183,16 +182,6 @@ class Item(EqBase): attr.info = info attr.value = val self.__attributes[info.name] = attr - try: - mod = __import__('overrides.' + str(self.ID), fromlist=True) - self.overrides = {} - for key in dir(mod): - if key[:2] != "__": - self.overrides[key] = getattr(mod, key) - except ImportError: - self.overrides = {} - - @reconstructor def init(self): @@ -201,6 +190,7 @@ class Item(EqBase): self.__moved = False self.__offensive = None self.__assistive = None + self.__overrides = None @property def attributes(self): @@ -220,6 +210,17 @@ 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.value + + return self.__overrides + @property def requiredSkills(self): if self.__requiredSkills is None: diff --git a/eos/types.py b/eos/types.py index 6e98749d2..5e21b0111 100644 --- a/eos/types.py +++ b/eos/types.py @@ -35,4 +35,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 From 0d0ec42daf74fdf26a0726e284705d82a2673107 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sun, 1 Nov 2015 19:49:22 -0500 Subject: [PATCH 06/12] Initial GUI commit. Still need to actually handle the attribute change --- eos/db/saveddata/queries.py | 3 + gui/PFSearchBox.py | 2 +- gui/mainFrame.py | 7 ++ gui/mainMenuBar.py | 5 ++ gui/marketBrowser.py | 4 +- gui/propertyEditor.py | 136 ++++++++++++++++++++++++++++++++++++ service/market.py | 24 +++++-- 7 files changed, 171 insertions(+), 10 deletions(-) create mode 100644 gui/propertyEditor.py diff --git a/eos/db/saveddata/queries.py b/eos/db/saveddata/queries.py index 3fbe55322..7b2853b6f 100644 --- a/eos/db/saveddata/queries.py +++ b/eos/db/saveddata/queries.py @@ -422,6 +422,9 @@ def getOverrides(itemID, eager=None): else: raise TypeError("Need integer as argument") +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/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/mainFrame.py b/gui/mainFrame.py index cb90ce0cc..4bb6bfb5d 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -53,6 +53,7 @@ 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 * from time import gmtime, strftime @@ -329,6 +330,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() @@ -416,6 +421,8 @@ 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) + # Open attribute editor + self.Bind(wx.EVT_MENU, self.showAttrEditor, id = menuBar.attrEditor) #Clipboard exports self.Bind(wx.EVT_MENU, self.exportToClipboard, id=wx.ID_COPY) diff --git a/gui/mainMenuBar.py b/gui/mainMenuBar.py index 7c971ae4d..555e86ca2 100644 --- a/gui/mainMenuBar.py +++ b/gui/mainMenuBar.py @@ -40,6 +40,7 @@ class MainMenuBar(wx.MenuBar): self.saveCharId = wx.NewId() self.saveCharAsId = wx.NewId() self.revertCharId = wx.NewId() + self.attrEditor = wx.NewId() self.mainFrame = gui.mainFrame.MainFrame.getInstance() @@ -102,6 +103,10 @@ class MainMenuBar(wx.MenuBar): preferencesItem.SetBitmap(BitmapLoader.getBitmap("preferences_small", "gui")) windowMenu.AppendItem(preferencesItem) + attrItem = wx.MenuItem(windowMenu, self.attrEditor, "Attribute Editor\tCTRL+A") + attrItem.SetBitmap(BitmapLoader.getBitmap("preferences_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..88e499119 --- /dev/null +++ b/gui/propertyEditor.py @@ -0,0 +1,136 @@ +import wx +import wx.propgrid as wxpg +import eos.db + +import gui.PFSearchBox as SBox +from gui.marketBrowser import SearchBox +import gui.display as d +import service + +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", size=wx.Size(700,500)) + + self.panel = panel = wx.Panel(self, wx.ID_ANY) + topsizer = wx.BoxSizer(wx.HORIZONTAL) + leftsizer = wx.BoxSizer(wx.VERTICAL) + + self.searchBox = SearchBox(panel, style=wx.DOUBLE_BORDER if 'wxMSW' in wx.PlatformInfo else wx.SIMPLE_BORDER) + self.itemView = ItemView(panel) + self.pg = AttributeGrid(panel) + + topsizer.Add(leftsizer, 1, wx.ALL|wx.EXPAND, 5) + topsizer.Add(self.pg, 1, wx.ALL|wx.EXPAND, 5) + + leftsizer.Add(self.searchBox, 0, wx.EXPAND) + leftsizer.Add(self.itemView, 1, wx.EXPAND) + + panel.SetSizer(topsizer) + topsizer.SetSizeHints(panel) + + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(panel, 1, wx.EXPAND) + self.SetSizer(sizer) + self.SetAutoLayout(True) + + +# 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) + self.parent = parent + sMkt = service.Market.getInstance() + self.things = sMkt.getItemsWithOverrides() + self.items = self.things + + # Bind search actions + parent.Parent.searchBox.Bind(SBox.EVT_TEXT_ENTER, self.scheduleSearch) + parent.Parent.searchBox.Bind(SBox.EVT_SEARCH_BTN, self.scheduleSearch) + parent.Parent.searchBox.Bind(SBox.EVT_CANCEL_BTN, self.clearSearch) + parent.Parent.searchBox.Bind(SBox.EVT_TEXT, self.scheduleSearch) + + self.update(self.items) + + def clearSearch(self, event=None): + if event: + self.parent.Parent.searchBox.Clear() + self.items = self.things + self.update(self.items) + + def scheduleSearch(self, event=None): + sMkt = service.Market.getInstance() + + search = self.parent.Parent.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 + + self.parent.searchMode = True + 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.parent = parent + + 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 ) + + parent.Parent.itemView.Bind(wx.EVT_LIST_ITEM_SELECTED, self.itemActivated) + parent.Parent.itemView.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.itemActivated) + + def itemActivated(self, event): + self.Clear() + sel = event.EventObject.GetFirstSelected() + item = self.parent.Parent.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 != item.attributes[key].value: + prop = wxpg.FloatProperty(key, value=override) + prop.defaultValue = default + prop.SetModifiedStatus(True) + else: + prop = wxpg.FloatProperty(key, value=default) + + self.Append(prop) + + def OnPropGridChange(self, event): + p = event.GetProperty() + if p: + logger.debug('%s changed to "%s"' % (p.GetName(), p.GetValueAsString())) + + def OnPropGridSelect(self, event): + p = event.GetProperty() + if p: + logger.debug('%s selected' % (event.GetProperty().GetName())) + else: + logger.debug('Nothing selected') + + def OnPropGridRightClick(self, event): + p = event.GetProperty() + if p: + logger.debug('%s right clicked' % (event.GetProperty().GetName())) + else: + logger.debug('Nothing right clicked') 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: From e80917e3d11acbe2b8fab6e760c22e78cffcb099 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sun, 1 Nov 2015 20:52:22 -0500 Subject: [PATCH 07/12] Things are saved now via GUI --- eos/gamedata.py | 24 ++++++++++++++++++- eos/modifiedAttributeDict.py | 2 +- gui/propertyEditor.py | 46 ++++++++++++++++++++++-------------- overrides/527.py | 1 - overrides/__init__.py | 0 5 files changed, 52 insertions(+), 21 deletions(-) delete mode 100644 overrides/527.py delete mode 100644 overrides/__init__.py diff --git a/eos/gamedata.py b/eos/gamedata.py index 2dda6703a..3e7c213eb 100644 --- a/eos/gamedata.py +++ b/eos/gamedata.py @@ -25,6 +25,7 @@ from eqBase import EqBase import traceback import eos.db + try: from collections import OrderedDict except ImportError: @@ -217,10 +218,25 @@ class Item(EqBase): overrides = eos.db.getOverrides(self.ID) for x in overrides: if x.attr.name in self.__attributes: - self.__overrides[x.attr.name] = x.value + 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: @@ -356,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 c5dda0f71..2dd79e3b9 100644 --- a/eos/modifiedAttributeDict.py +++ b/eos/modifiedAttributeDict.py @@ -113,7 +113,7 @@ class ModifiedAttributeDict(collections.MutableMapping): def getOriginal(self, key): if self.OVERRIDES and key in self.__overrides: - return self.__overrides.get(key) + return self.__overrides.get(key).value val = self.__original.get(key) if val is None: return None diff --git a/gui/propertyEditor.py b/gui/propertyEditor.py index 88e499119..0a6b57407 100644 --- a/gui/propertyEditor.py +++ b/gui/propertyEditor.py @@ -1,11 +1,11 @@ import wx import wx.propgrid as wxpg -import eos.db import gui.PFSearchBox as SBox from gui.marketBrowser import SearchBox import gui.display as d import service +import gui.globalEvents as GE import logging @@ -15,7 +15,7 @@ class AttributeEditor( wx.Frame ): def __init__( self, parent ): wx.Frame.__init__(self, parent, wx.ID_ANY, title="Attribute Editor", size=wx.Size(700,500)) - + self.mainFrame = parent self.panel = panel = wx.Panel(self, wx.ID_ANY) topsizer = wx.BoxSizer(wx.HORIZONTAL) leftsizer = wx.BoxSizer(wx.VERTICAL) @@ -38,6 +38,14 @@ class AttributeEditor( wx.Frame ): 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() + # This is literally a stripped down version of the market. class ItemView(d.Display): @@ -90,7 +98,10 @@ 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.parent = parent + self.item = None self.Bind( wxpg.EVT_PG_CHANGED, self.OnPropGridChange ) self.Bind( wxpg.EVT_PG_SELECTED, self.OnPropGridSelect ) @@ -102,35 +113,34 @@ class AttributeGrid(wxpg.PropertyGrid): def itemActivated(self, event): self.Clear() sel = event.EventObject.GetFirstSelected() - item = self.parent.Parent.itemView.items[sel] + self.item = item = self.parent.Parent.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 != item.attributes[key].value: - prop = wxpg.FloatProperty(key, value=override) - prop.defaultValue = default + 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.2f"%default)) self.Append(prop) def OnPropGridChange(self, event): p = event.GetProperty() - if p: - logger.debug('%s changed to "%s"' % (p.GetName(), p.GetValueAsString())) + attr = p.GetClientData() + if p.GetValue() == attr.value: + self.item.deleteOverride(attr) + p.SetModifiedStatus(False) + else: + self.item.setOverride(attr, p.GetValue()) + + logger.debug('%s changed to "%s"' % (p.GetName(), p.GetValueAsString())) def OnPropGridSelect(self, event): - p = event.GetProperty() - if p: - logger.debug('%s selected' % (event.GetProperty().GetName())) - else: - logger.debug('Nothing selected') + pass def OnPropGridRightClick(self, event): - p = event.GetProperty() - if p: - logger.debug('%s right clicked' % (event.GetProperty().GetName())) - else: - logger.debug('Nothing right clicked') + pass diff --git a/overrides/527.py b/overrides/527.py deleted file mode 100644 index b1c82b796..000000000 --- a/overrides/527.py +++ /dev/null @@ -1 +0,0 @@ -speedFactor = -80.0 diff --git a/overrides/__init__.py b/overrides/__init__.py deleted file mode 100644 index e69de29bb..000000000 From c436e0e669bc16fa59defa848d0a40d9edcc6880 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sun, 1 Nov 2015 21:19:49 -0500 Subject: [PATCH 08/12] Create a proper menu item toggle for overrides --- eos/modifiedAttributeDict.py | 2 +- gui/characterEditor.py | 9 --------- gui/mainFrame.py | 13 ++++++++++++- gui/mainMenuBar.py | 10 +++++++--- gui/propertyEditor.py | 10 ++++++++-- 5 files changed, 28 insertions(+), 16 deletions(-) diff --git a/eos/modifiedAttributeDict.py b/eos/modifiedAttributeDict.py index 2dd79e3b9..f53384069 100644 --- a/eos/modifiedAttributeDict.py +++ b/eos/modifiedAttributeDict.py @@ -39,7 +39,7 @@ class ChargeAttrShortcut(object): class ModifiedAttributeDict(collections.MutableMapping): - OVERRIDES = True + OVERRIDES = False class CalculationPlaceholder(): pass diff --git a/gui/characterEditor.py b/gui/characterEditor.py index 4308f25ed..0cebb5101 100644 --- a/gui/characterEditor.py +++ b/gui/characterEditor.py @@ -29,21 +29,12 @@ from gui.contextMenu import ContextMenu from wx.lib.buttons import GenBitmapButton import gui.globalEvents as GE -from eos.modifiedAttributeDict import ModifiedAttributeDict - class CharacterEditor(wx.Frame): def __init__(self, parent): wx.Frame.__init__ (self, parent, id=wx.ID_ANY, title=u"pyfa: Character Editor", pos=wx.DefaultPosition, size=wx.Size(641, 600), style=wx.DEFAULT_FRAME_STYLE|wx.FRAME_FLOAT_ON_PARENT|wx.TAB_TRAVERSAL) i = wx.IconFromBitmap(BitmapLoader.getBitmap("character_small", "gui")) - - print ModifiedAttributeDict.OVERRIDES - ModifiedAttributeDict.OVERRIDES = not ModifiedAttributeDict.OVERRIDES - print ModifiedAttributeDict.OVERRIDES - - self.mainFrame = parent - self.SetIcon(i) self.disableWin= wx.WindowDisabler(self) diff --git a/gui/mainFrame.py b/gui/mainFrame.py index 4bb6bfb5d..ff0ce1640 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -56,6 +56,9 @@ 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 @@ -422,7 +425,9 @@ class MainFrame(wx.Frame): # Save current character self.Bind(wx.EVT_MENU, self.revertChar, id = menuBar.revertCharId) # Open attribute editor - self.Bind(wx.EVT_MENU, self.showAttrEditor, id = menuBar.attrEditor) + self.Bind(wx.EVT_MENU, self.showAttrEditor, id = menuBar.attrEditorId) + # Open attribute editor + self.Bind(wx.EVT_MENU, self.toggleOverrides, id = menuBar.toggleOverridesId) #Clipboard exports self.Bind(wx.EVT_MENU, self.exportToClipboard, id=wx.ID_COPY) @@ -487,6 +492,12 @@ class MainFrame(wx.Frame): atable = wx.AcceleratorTable(actb) self.SetAcceleratorTable(atable) + 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 555e86ca2..1d3f6378e 100644 --- a/gui/mainMenuBar.py +++ b/gui/mainMenuBar.py @@ -40,7 +40,8 @@ class MainMenuBar(wx.MenuBar): self.saveCharId = wx.NewId() self.saveCharAsId = wx.NewId() self.revertCharId = wx.NewId() - self.attrEditor = wx.NewId() + self.attrEditorId = wx.NewId() + self.toggleOverridesId = wx.NewId() self.mainFrame = gui.mainFrame.MainFrame.getInstance() @@ -79,6 +80,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") @@ -103,8 +107,8 @@ class MainMenuBar(wx.MenuBar): preferencesItem.SetBitmap(BitmapLoader.getBitmap("preferences_small", "gui")) windowMenu.AppendItem(preferencesItem) - attrItem = wx.MenuItem(windowMenu, self.attrEditor, "Attribute Editor\tCTRL+A") - attrItem.SetBitmap(BitmapLoader.getBitmap("preferences_small", "gui")) + attrItem = wx.MenuItem(windowMenu, self.attrEditorId, "Attribute Overrides\tCTRL+A") + attrItem.SetBitmap(BitmapLoader.getBitmap("fit_rename_small", "gui")) windowMenu.AppendItem(attrItem) # Help menu diff --git a/gui/propertyEditor.py b/gui/propertyEditor.py index 0a6b57407..60eb8ab2f 100644 --- a/gui/propertyEditor.py +++ b/gui/propertyEditor.py @@ -4,8 +4,9 @@ import wx.propgrid as wxpg import gui.PFSearchBox as SBox from gui.marketBrowser import SearchBox import gui.display as d -import service import gui.globalEvents as GE +from gui.bitmapLoader import BitmapLoader +import service import logging @@ -14,7 +15,12 @@ logger = logging.getLogger(__name__) class AttributeEditor( wx.Frame ): def __init__( self, parent ): - wx.Frame.__init__(self, parent, wx.ID_ANY, title="Attribute Editor", size=wx.Size(700,500)) + 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 self.panel = panel = wx.Panel(self, wx.ID_ANY) topsizer = wx.BoxSizer(wx.HORIZONTAL) From d9c710c5a1b37c6568f8ae7ee780f62b0de7f77a Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sun, 1 Nov 2015 21:36:04 -0500 Subject: [PATCH 09/12] Minor improvements --- gui/propertyEditor.py | 47 ++++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/gui/propertyEditor.py b/gui/propertyEditor.py index 60eb8ab2f..4172985ef 100644 --- a/gui/propertyEditor.py +++ b/gui/propertyEditor.py @@ -24,18 +24,22 @@ class AttributeEditor( wx.Frame ): self.mainFrame = parent self.panel = panel = wx.Panel(self, wx.ID_ANY) topsizer = 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(panel, style=wx.DOUBLE_BORDER if 'wxMSW' in wx.PlatformInfo else wx.SIMPLE_BORDER) - self.itemView = ItemView(panel) - self.pg = AttributeGrid(panel) - - topsizer.Add(leftsizer, 1, wx.ALL|wx.EXPAND, 5) - topsizer.Add(self.pg, 1, wx.ALL|wx.EXPAND, 5) + 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) + topsizer.Add(leftPanel, 1, wx.ALL | wx.EXPAND, 5) + + self.pg = AttributeGrid(panel) + topsizer.Add(self.pg, 1, wx.ALL|wx.EXPAND, 5) + panel.SetSizer(topsizer) topsizer.SetSizeHints(panel) @@ -62,29 +66,34 @@ class ItemView(d.Display): def __init__(self, parent): d.Display.__init__(self, parent) - self.parent = parent sMkt = service.Market.getInstance() + self.things = sMkt.getItemsWithOverrides() self.items = self.things + self.searchBox = parent.Parent.Parent.searchBox # Bind search actions - parent.Parent.searchBox.Bind(SBox.EVT_TEXT_ENTER, self.scheduleSearch) - parent.Parent.searchBox.Bind(SBox.EVT_SEARCH_BTN, self.scheduleSearch) - parent.Parent.searchBox.Bind(SBox.EVT_CANCEL_BTN, self.clearSearch) - parent.Parent.searchBox.Bind(SBox.EVT_TEXT, self.scheduleSearch) + 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.parent.Parent.searchBox.Clear() + self.searchBox.Clear() self.items = self.things self.update(self.items) + def updateItems(self): + sMkt = service.Market.getInstance() + self.things = sMkt.getItemsWithOverrides() + def scheduleSearch(self, event=None): sMkt = service.Market.getInstance() - search = self.parent.Parent.searchBox.GetLineText(0) + 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 @@ -92,7 +101,6 @@ class ItemView(d.Display): self.clearSearch() return - self.parent.searchMode = True sMkt.searchItems(search, self.populateSearch, False) def populateSearch(self, items): @@ -106,20 +114,21 @@ class AttributeGrid(wxpg.PropertyGrid): 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.parent = parent self.item = None + self.itemView = parent.Parent.itemView + 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 ) - parent.Parent.itemView.Bind(wx.EVT_LIST_ITEM_SELECTED, self.itemActivated) - parent.Parent.itemView.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.itemActivated) + 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() sel = event.EventObject.GetFirstSelected() - self.item = item = self.parent.Parent.itemView.items[sel] + self.item = item = self.itemView.items[sel] for key in sorted(item.attributes.keys()): override = item.overrides.get(key, None) @@ -143,6 +152,8 @@ class AttributeGrid(wxpg.PropertyGrid): else: self.item.setOverride(attr, p.GetValue()) + self.itemView.updateItems() + logger.debug('%s changed to "%s"' % (p.GetName(), p.GetValueAsString())) def OnPropGridSelect(self, event): From ef36dc5ba3f4c3fb4fc29608962902dbfc107fe5 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Mon, 2 Nov 2015 18:42:01 -0500 Subject: [PATCH 10/12] Import/export/clear overrides, add missing files due to bad gitignore entry --- eos/db/saveddata/override.py | 31 ++++++++++ eos/db/saveddata/queries.py | 6 ++ eos/saveddata/override.py | 59 ++++++++++++++++++ gui/propertyEditor.py | 117 +++++++++++++++++++++++++++++++---- 4 files changed, 201 insertions(+), 12 deletions(-) create mode 100644 eos/db/saveddata/override.py create mode 100644 eos/saveddata/override.py 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 7b2853b6f..172093191 100644 --- a/eos/db/saveddata/queries.py +++ b/eos/db/saveddata/queries.py @@ -422,6 +422,12 @@ def getOverrides(itemID, eager=None): 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() 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/gui/propertyEditor.py b/gui/propertyEditor.py index 4172985ef..44c63307b 100644 --- a/gui/propertyEditor.py +++ b/gui/propertyEditor.py @@ -7,6 +7,8 @@ import gui.display as d import gui.globalEvents as GE from gui.bitmapLoader import BitmapLoader import service +import csv +import eos.db import logging @@ -22,26 +24,53 @@ class AttributeEditor( wx.Frame ): self.SetIcon(i) self.mainFrame = parent - self.panel = panel = wx.Panel(self, wx.ID_ANY) - topsizer = wx.BoxSizer(wx.HORIZONTAL) - leftsizer = wx.BoxSizer(wx.VERTICAL) + 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) + leftSizer.Add(self.searchBox, 0, wx.EXPAND) + leftSizer.Add(self.itemView, 1, wx.EXPAND) - leftPanel.SetSizer(leftsizer) - topsizer.Add(leftPanel, 1, wx.ALL | wx.EXPAND, 5) + 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) - topsizer.Add(self.pg, 1, wx.ALL|wx.EXPAND, 5) + 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) - panel.SetSizer(topsizer) - topsizer.SetSizeHints(panel) + mainSizer.Add(rightSizer, 1, wx.EXPAND) + + panel.SetSizer(mainSizer) + mainSizer.SetSizeHints(panel) sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(panel, 1, wx.EXPAND) @@ -56,6 +85,48 @@ class AttributeEditor( wx.Frame ): 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: + eos.db.clearOverrides() + self.itemView.updateItems(True) + self.pg.Clear() # This is literally a stripped down version of the market. class ItemView(d.Display): @@ -86,9 +157,12 @@ class ItemView(d.Display): self.items = self.things self.update(self.items) - def updateItems(self): + 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() @@ -118,6 +192,8 @@ class AttributeGrid(wxpg.PropertyGrid): 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 ) @@ -127,6 +203,7 @@ class AttributeGrid(wxpg.PropertyGrid): def itemActivated(self, event): self.Clear() + self.btn.Enable(True) sel = event.EventObject.GetFirstSelected() self.item = item = self.itemView.items[sel] @@ -140,9 +217,25 @@ class AttributeGrid(wxpg.PropertyGrid): 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.2f"%default)) + 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() From eb5ce91cb20b6035bc03eee27c014d11ced5d6ef Mon Sep 17 00:00:00 2001 From: blitzmann Date: Mon, 2 Nov 2015 18:47:06 -0500 Subject: [PATCH 11/12] Fix gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 96e9e15a3..13032ec18 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,7 @@ *.patch #Personal -saveddata/ +/saveddata/ #PyCharm .idea/ From 3c055b78d39d613ade54f0934dd4162be6d259b3 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Mon, 2 Nov 2015 18:55:21 -0500 Subject: [PATCH 12/12] Fix clearing of all overrides --- gui/propertyEditor.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/gui/propertyEditor.py b/gui/propertyEditor.py index 44c63307b..406007eb3 100644 --- a/gui/propertyEditor.py +++ b/gui/propertyEditor.py @@ -124,7 +124,15 @@ class AttributeEditor( wx.Frame ): "Confirm Delete", wx.YES | wx.NO | wx.ICON_EXCLAMATION) if dlg.ShowModal() == wx.ID_YES: - eos.db.clearOverrides() + 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()