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: