From 5df7e193e77b69332eee790440589f242dfca1c4 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Wed, 26 Mar 2014 22:41:51 -0400 Subject: [PATCH] Cargo works, but needs a more TLC --- eos/db/saveddata/cargo.py | 32 +++++++ eos/db/saveddata/fit.py | 7 +- eos/effectHandlerHelpers.py | 40 +++++++++ eos/saveddata/cargo.py | 78 +++++++++++++++++ eos/saveddata/fit.py | 20 ++++- eos/types.py | 1 + gui/additionsPane.py | 5 +- gui/builtinContextMenus/__init__.py | 2 +- gui/builtinContextMenus/cargo.py | 101 ++++++++++++++++++++++ gui/builtinStatsViews/priceViewFull.py | 3 + gui/builtinViewColumns/baseName.py | 4 +- gui/cargoView.py | 115 +++++++++++++++++++++++++ icons/cargo_small.png | Bin 0 -> 669 bytes service/fit.py | 41 +++++++++ 14 files changed, 442 insertions(+), 7 deletions(-) create mode 100644 eos/db/saveddata/cargo.py create mode 100644 eos/saveddata/cargo.py create mode 100644 gui/builtinContextMenus/cargo.py create mode 100644 gui/cargoView.py create mode 100644 icons/cargo_small.png diff --git a/eos/db/saveddata/cargo.py b/eos/db/saveddata/cargo.py new file mode 100644 index 000000000..deabd3b2e --- /dev/null +++ b/eos/db/saveddata/cargo.py @@ -0,0 +1,32 @@ +#=============================================================================== +# 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, ForeignKey, Boolean +from sqlalchemy.orm import mapper + +from eos.db import saveddata_meta +from eos.types import Cargo + +cargo_table = Table("cargo", saveddata_meta, + Column("ID", Integer, primary_key=True), + Column("fitID", Integer, ForeignKey("fits.ID"), nullable = False, index = True), + Column("itemID", Integer, nullable = False), + Column("amount", Integer, nullable = False)) + +mapper(Cargo, cargo_table) \ No newline at end of file diff --git a/eos/db/saveddata/fit.py b/eos/db/saveddata/fit.py index 25516b7b6..ccb8bbb71 100755 --- a/eos/db/saveddata/fit.py +++ b/eos/db/saveddata/fit.py @@ -24,11 +24,12 @@ from sqlalchemy.sql import and_ from eos.db import saveddata_meta from eos.db.saveddata.module import modules_table from eos.db.saveddata.drone import drones_table +from eos.db.saveddata.cargo import cargo_table from eos.db.saveddata.implant import fitImplants_table -from eos.types import Fit, Module, User, Booster, Drone, Implant, Character, DamagePattern +from eos.types import Fit, Module, User, Booster, Drone, Cargo, Implant, Character, DamagePattern from eos.effectHandlerHelpers import HandledModuleList, HandledDroneList, \ HandledImplantBoosterList, HandledProjectedModList, HandledProjectedDroneList, \ -HandledProjectedFitList +HandledProjectedFitList, HandledCargoList fits_table = Table("fits", saveddata_meta, Column("ID", Integer, primary_key = True), Column("ownerID", ForeignKey("users.ID"), nullable = True, index = True), @@ -53,6 +54,8 @@ mapper(Fit, fits_table, "_Fit__boosters" : relation(Booster, collection_class = HandledImplantBoosterList, cascade='all, delete, delete-orphan', single_parent=True), "_Fit__drones" : relation(Drone, collection_class = HandledDroneList, cascade='all, delete, delete-orphan', single_parent=True, primaryjoin = and_(drones_table.c.fitID == fits_table.c.ID, drones_table.c.projected == False)), + "_Fit__cargo" : relation(Cargo, collection_class = HandledCargoList, cascade='all, delete, delete-orphan', single_parent=True, + primaryjoin = and_(cargo_table.c.fitID == fits_table.c.ID)), "_Fit__projectedDrones" : relation(Drone, collection_class = HandledProjectedDroneList, cascade='all, delete, delete-orphan', single_parent=True, primaryjoin = and_(drones_table.c.fitID == fits_table.c.ID, drones_table.c.projected == True)), "_Fit__implants" : relation(Implant, collection_class = HandledImplantBoosterList, cascade='all, delete, delete-orphan', single_parent=True, diff --git a/eos/effectHandlerHelpers.py b/eos/effectHandlerHelpers.py index c4b66fb94..b3b4c75ee 100755 --- a/eos/effectHandlerHelpers.py +++ b/eos/effectHandlerHelpers.py @@ -187,6 +187,46 @@ class HandledDroneList(HandledList): return d +class HandledCargoList(HandledList): + # shameless copy of HandledDroneList + # I have no idea what this does, but I needed it + # @todo: investigate this + def find(self, item): + for d in self: + if d.item == item: + yield d + + def findFirst(self, item): + for d in self.find(item): + return d + + def append(self, cargo): + list.append(self, cargo) + + def remove(self, cargo): + HandledList.remove(self, cargo) + + def appendItem(self, item, qty = 1): + if qty < 1: ValueError("Amount of cargo to add should be >= 1") + d = self.findFirst(item) + + if d is None: + d = eos.types.Cargo(item) + self.append(d) + + d.qty += qty + return d + + def removeItem(self, item, qty): + if qty < 1: ValueError("Amount of cargo to remove should be >= 1") + d = self.findFirst(item) + if d is None: return + d.qty -= qty + if d.qty <= 0: + self.remove(d) + return None + + return d class HandledImplantBoosterList(HandledList): def __init__(self): diff --git a/eos/saveddata/cargo.py b/eos/saveddata/cargo.py new file mode 100644 index 000000000..5ad050515 --- /dev/null +++ b/eos/saveddata/cargo.py @@ -0,0 +1,78 @@ +#=============================================================================== +# 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 eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut +from eos.effectHandlerHelpers import HandledItem, HandledCharge +from sqlalchemy.orm import validates, reconstructor + +# Cargo class copied from Implant class and hacked to make work. \o/ +# @todo: clean me up, Scotty +class Cargo(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): + + def __init__(self, item): + if item.category.name != "Charge": + raise ValueError("Passed item is not a charge") + + self.__item = item + self.itemID = item.ID + self.active = True + self.amount = 0 + self.__itemModifiedAttributes = ModifiedAttributeDict() + self.__itemModifiedAttributes.original = self.item.attributes + + @reconstructor + def init(self): + self.__item = None + + def __fetchItemInfo(self): + import eos.db + self.__item = eos.db.getItem(self.itemID) + self.__itemModifiedAttributes = ModifiedAttributeDict() + self.__itemModifiedAttributes.original = self.__item.attributes + + @property + def itemModifiedAttributes(self): + if self.__item is None: + self.__fetchItemInfo() + + return self.__itemModifiedAttributes + + @property + def item(self): + if self.__item is None: + self.__fetchItemInfo() + + return self.__item + + def clear(self): + self.itemModifiedAttributes.clear() + + @validates("fitID", "itemID", "active") + def validator(self, key, val): + map = {"fitID": lambda val: isinstance(val, int), + "itemID" : lambda val: isinstance(val, int), + "active": lambda val: isinstance(val, bool)} + + if map[key](val) == False: raise ValueError(str(val) + " is not a valid value for " + key) + else: return val + + def __deepcopy__(self, memo): + copy = Cargo(self.item) + copy.active = self.active + return copy \ No newline at end of file diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py index 446588f9e..418053843 100755 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -18,14 +18,14 @@ #=============================================================================== from eos.effectHandlerHelpers import HandledList, HandledModuleList, HandledDroneList, HandledImplantBoosterList, \ -HandledProjectedFitList, HandledProjectedModList, HandledProjectedDroneList +HandledProjectedFitList, HandledProjectedModList, HandledProjectedDroneList, HandledCargoList from eos.modifiedAttributeDict import ModifiedAttributeDict from sqlalchemy.orm import validates, reconstructor from itertools import chain from eos import capSim from copy import deepcopy from math import sqrt, log, asinh -from eos.types import Drone, Ship, Character, State, Slot, Module, Implant, Booster, Skill +from eos.types import Drone, Cargo, Ship, Character, State, Slot, Module, Implant, Booster, Skill from eos.saveddata.module import State import re import xml.dom @@ -52,6 +52,7 @@ class Fit(object): def __init__(self): self.__modules = HandledModuleList() self.__drones = HandledDroneList() + self.__cargo = HandledCargoList() self.__implants = HandledImplantBoosterList() self.__boosters = HandledImplantBoosterList() self.__projectedFits = HandledProjectedFitList() @@ -425,6 +426,10 @@ class Fit(object): d = Drone(item) d.amount = int(hardware.getAttribute("qty")) f.drones.append(d) + elif item.category.name == "Charge": + c = Cargo(item) + c.amount = int(hardware.getAttribute("qty")) + f.cargo.append(c) else: try: m = Module(item) @@ -545,6 +550,13 @@ class Fit(object): hardware.setAttribute("type", drone.item.name) fitting.appendChild(hardware) + for cargo in fit.cargo: + hardware = doc.createElement("hardware") + hardware.setAttribute("qty", "%d" % cargo.amount) + hardware.setAttribute("slot", "cargo") + hardware.setAttribute("type", cargo.item.name) + fitting.appendChild(hardware) + return doc.toprettyxml() @reconstructor @@ -606,6 +618,10 @@ class Fit(object): def drones(self): return self.__drones + @property + def cargo(self): + return self.__cargo + @property def modules(self): return self.__modules diff --git a/eos/types.py b/eos/types.py index 358e0828c..9896e526a 100755 --- a/eos/types.py +++ b/eos/types.py @@ -25,6 +25,7 @@ from eos.saveddata.damagePattern import DamagePattern from eos.saveddata.character import Character, Skill from eos.saveddata.module import Module, State, Slot, Hardpoint, Rack from eos.saveddata.drone import Drone +from eos.saveddata.cargo import Cargo from eos.saveddata.implant import Implant from eos.saveddata.booster import SideEffect from eos.saveddata.booster import Booster diff --git a/gui/additionsPane.py b/gui/additionsPane.py index a17357654..449d3935b 100644 --- a/gui/additionsPane.py +++ b/gui/additionsPane.py @@ -21,6 +21,7 @@ import wx import gui.mainFrame from gui.boosterView import BoosterView from gui.droneView import DroneView +from gui.cargoView import CargoView from gui.implantView import ImplantView from gui.projectedView import ProjectedView from gui.pyfatogglepanel import TogglePanel @@ -55,8 +56,10 @@ class AdditionsPane(TogglePanel): boosterImg = bitmapLoader.getImage("booster_small", "icons") projectedImg = bitmapLoader.getImage("projected_small", "icons") gangImg = bitmapLoader.getImage("fleet_fc_small", "icons") + cargoImg = bitmapLoader.getImage("cargo_small", "icons") self.notebook.AddPage(DroneView(self.notebook), "Drones", tabImage = droneImg, showClose = False) + self.notebook.AddPage(CargoView(self.notebook), "Cargo", tabImage = cargoImg, showClose = False) self.notebook.AddPage(ImplantView(self.notebook), "Implants", tabImage = implantImg, showClose = False) self.notebook.AddPage(BoosterView(self.notebook), "Boosters", tabImage = boosterImg, showClose = False) @@ -68,6 +71,6 @@ class AdditionsPane(TogglePanel): self.notebook.SetSelection(0) - PANES = ["Drones", "Implants", "Boosters", "Projected", "Fleet"] + PANES = ["Drones", "Cargo", "Implants", "Boosters", "Projected", "Fleet"] def select(self, name): self.notebook.SetSelection(self.PANES.index(name)) diff --git a/gui/builtinContextMenus/__init__.py b/gui/builtinContextMenus/__init__.py index e50949d3f..edec6a9fc 100644 --- a/gui/builtinContextMenus/__init__.py +++ b/gui/builtinContextMenus/__init__.py @@ -1,2 +1,2 @@ __all__ = ["moduleAmmoPicker", "itemStats", "damagePattern", "marketJump", "droneSplit", "itemRemove", - "droneRemoveStack", "ammoPattern", "project", "factorReload", "whProjector"] + "droneRemoveStack", "ammoPattern", "project", "factorReload", "whProjector", "cargo"] diff --git a/gui/builtinContextMenus/cargo.py b/gui/builtinContextMenus/cargo.py new file mode 100644 index 000000000..8e0512588 --- /dev/null +++ b/gui/builtinContextMenus/cargo.py @@ -0,0 +1,101 @@ +from gui.contextMenu import ContextMenu +from gui.itemStats import ItemStatsDialog +import eos.types +import gui.mainFrame +import service +import gui.globalEvents as GE +import wx + +class Cargo(ContextMenu): + def __init__(self): + self.mainFrame = gui.mainFrame.MainFrame.getInstance() + + def display(self, srcContext, selection): + # Make sure context menu registers in the correct view + if srcContext not in ("marketItemGroup", "marketItemMisc") or self.mainFrame.getActiveFit() is None: + return False + + item = selection[0] + sFit = service.Fit.getInstance() + + return sFit.isAmmo(item.ID) + + def getText(self, itmContext, selection): + return "Add {0} to Cargo".format(itmContext) + + def activate(self, fullContext, selection, i): + sFit = service.Fit.getInstance() + fitID = self.mainFrame.getActiveFit() + + cargo = eos.types.Cargo(selection[0]) + + dlg = CargoChanger(self.mainFrame, cargo, fullContext) + dlg.ShowModal() + dlg.Destroy() + + wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID)) + +Cargo.register() + +class CargoAmount(ContextMenu): + def __init__(self): + self.mainFrame = gui.mainFrame.MainFrame.getInstance() + + def display(self, srcContext, selection): + return srcContext in ("cargoItem",) and selection[0].amount >= 0 + + def getText(self, itmContext, selection): + return "Change {0} Quantity".format(itmContext) + + def activate(self, fullContext, selection, i): + srcContext = fullContext[0] + dlg = CargoChanger(self.mainFrame, selection[0], srcContext) + dlg.ShowModal() + dlg.Destroy() + +CargoAmount.register() + +class CargoChanger(wx.Dialog): + + def __init__(self, parent, cargo, context): + wx.Dialog.__init__(self, parent, title="Select Amount", size=wx.Size(220, 60)) + self.cargo = cargo + self.context = context + + bSizer1 = wx.BoxSizer(wx.HORIZONTAL) + + self.input = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0) + + bSizer1.Add(self.input, 0, wx.ALL, 5) + self.input.Bind(wx.EVT_CHAR, self.onChar) + self.button = wx.Button(self, wx.ID_OK, u"Change") + bSizer1.Add(self.button, 0, wx.ALL, 5) + + self.SetSizer(bSizer1) + self.Layout() + self.Centre(wx.BOTH) + self.button.Bind(wx.EVT_BUTTON, self.change) + + def change(self, event): + sFit = service.Fit.getInstance() + mainFrame = gui.mainFrame.MainFrame.getInstance() + fitID = mainFrame.getActiveFit() + + sFit.addChangeCargo(fitID, self.cargo, int(self.input.GetLineText(0))) + + wx.PostEvent(mainFrame, GE.FitChanged(fitID=fitID)) + event.Skip() + + ## checks to make sure it's valid number + def onChar(self, event): + key = event.GetKeyCode() + + acceptable_characters = "1234567890" + acceptable_keycode = [3, 22, 13, 8, 127] # modifiers like delete, copy, paste + + if key in acceptable_keycode or key >= 255 or (key < 255 and chr(key) in acceptable_characters): + event.Skip() + return + else: + return False + diff --git a/gui/builtinStatsViews/priceViewFull.py b/gui/builtinStatsViews/priceViewFull.py index 0b44d209d..801948a3a 100644 --- a/gui/builtinStatsViews/priceViewFull.py +++ b/gui/builtinStatsViews/priceViewFull.py @@ -102,6 +102,9 @@ class PriceViewFull(StatsView): for drone in fit.drones: for _ in xrange(drone.amount): typeIDs.append(drone.itemID) + for cargo in fit.cargo: + for _ in xrange(cargo.amount): + typeIDs.append(cargo.itemID) if self._timer: if self._timer.IsRunning(): self._timer.Stop() diff --git a/gui/builtinViewColumns/baseName.py b/gui/builtinViewColumns/baseName.py index 5e40a75cf..5b2a65d58 100644 --- a/gui/builtinViewColumns/baseName.py +++ b/gui/builtinViewColumns/baseName.py @@ -22,7 +22,7 @@ from gui import builtinViewColumns from gui.viewColumn import ViewColumn from gui import bitmapLoader import wx -from eos.types import Drone, Fit, Module, Slot, Rack +from eos.types import Drone, Cargo, Fit, Module, Slot, Rack import service class BaseName(ViewColumn): @@ -36,6 +36,8 @@ class BaseName(ViewColumn): def getText(self, stuff): if isinstance(stuff, Drone): return "%dx %s" % (stuff.amount, stuff.item.name) + elif isinstance(stuff, Cargo): + return "%dx %s" % (stuff.amount, stuff.item.name) elif isinstance(stuff, Fit): return "%s (%s)" % (stuff.name, stuff.ship.item.name) elif isinstance(stuff, Rack): diff --git a/gui/cargoView.py b/gui/cargoView.py new file mode 100644 index 000000000..7f113b4fd --- /dev/null +++ b/gui/cargoView.py @@ -0,0 +1,115 @@ +#=============================================================================== +# Copyright (C) 2010 Diego Duclos +# +# This file is part of pyfa. +# +# pyfa is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# pyfa is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with pyfa. If not, see . +#=============================================================================== + +import wx +import service +import gui.display as d +import gui.marketBrowser as mb +from gui.builtinViewColumns.state import State +from gui.contextMenu import ContextMenu +import globalEvents as GE + +# @todo: Was copied form another class and modified. Look through entire file, refine +class CargoView(d.Display): + DEFAULT_COLS = ["Base Name"] + + def __init__(self, parent): + d.Display.__init__(self, parent, style=wx.LC_SINGLE_SEL | wx.BORDER_NONE) + + self.lastFitId = None + + self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged) + #self.mainFrame.Bind(mb.ITEM_SELECTED, self.addItem) + self.Bind(wx.EVT_LEFT_DCLICK, self.removeItem) + self.Bind(wx.EVT_KEY_UP, self.kbEvent) + + if "__WXGTK__" in wx.PlatformInfo: + self.Bind(wx.EVT_RIGHT_UP, self.scheduleMenu) + else: + self.Bind(wx.EVT_RIGHT_DOWN, self.scheduleMenu) + + def kbEvent(self,event): + keycode = event.GetKeyCode() + if keycode == wx.WXK_DELETE or keycode == wx.WXK_NUMPAD_DELETE: + fitID = self.mainFrame.getActiveFit() + cFit = service.Fit.getInstance() + row = self.GetFirstSelected() + if row != -1: + cFit.removeCargo(fitID, self.GetItemData(row)) + wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID)) + event.Skip() + + def fitChanged(self, event): + #Clear list and get out if current fitId is None + if event.fitID is None and self.lastFitId is not None: + self.DeleteAllItems() + self.lastFitId = None + event.Skip() + return + + cFit = service.Fit.getInstance() + fit = cFit.getFit(event.fitID) + + self.original = fit.cargo if fit is not None else None + self.cargo = stuff = fit.cargo if fit is not None else None + if stuff is not None: stuff.sort(key=lambda cargo: cargo.itemID) + + if event.fitID != self.lastFitId: + self.lastFitId = event.fitID + + item = self.GetNextItem(-1, wx.LIST_NEXT_ALL, wx.LIST_STATE_DONTCARE) + + if item != -1: + self.EnsureVisible(item) + + self.deselectItems() + + self.populate(stuff) + self.refresh(stuff) + event.Skip() + + def removeItem(self, event): + row, _ = self.HitTest(event.Position) + if row != -1: + col = self.getColumn(event.Position) + if col != self.getColIndex(State): + fitID = self.mainFrame.getActiveFit() + cFit = service.Fit.getInstance() + cargo = self.cargo[self.GetItemData(row)] + cFit.removeCargo(fitID, self.original.index(cargo)) + wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID)) + + def scheduleMenu(self, event): + event.Skip() + if self.getColumn(event.Position) != self.getColIndex(State): + wx.CallAfter(self.spawnMenu) + + def spawnMenu(self): + sel = self.GetFirstSelected() + if sel != -1: + cFit = service.Fit.getInstance() + fit = cFit.getFit(self.mainFrame.getActiveFit()) + cargo = fit.cargo[sel] + + sMkt = service.Market.getInstance() + sourceContext = "cargoItem" + itemContext = sMkt.getCategoryByItem(cargo.item).name + + menu = ContextMenu.getMenu((cargo,), (sourceContext, itemContext)) + self.PopupMenu(menu) diff --git a/icons/cargo_small.png b/icons/cargo_small.png new file mode 100644 index 0000000000000000000000000000000000000000..2906b92141f25e0712a071b3c0e75cec7bb2f4b9 GIT binary patch literal 669 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47<~hLLR^8||Nnpe|NnndOWvCo z_s$;M))H+}n&jheqxR~_jpZ{tOyoJfeR=);_n#lXetrG^<@1*hcW$1YH?7gnR=ux2 zpNWy-^7$jn=5zv0*s@~g$^DzIoIiB-{~t-LZ0NOF?RowGD~9AKfeF|_4V85vxhdP1X$_Iamxy^@q)buQFHtH>9=p6fBg9N z#nXGe4S5!7g2Fr?k0XOg9fdpB&p)_*rG^9t&=+u!8R61gl#myWfRaBNPNsV|| zV^$WqQ>T*Em2U=v`t(Vgx>n7yGM{q(@alytm#&?>cJ*@pN1r}QOGru1o-u1?bVO7m z3(K0^oVRaeW#-O_jk)W<)bvhnUfeyqeRcmB8yyuG7e1IU=fa&0A39EGbWA*?bkVSK yW8p{6g76MCHPz)ymc3NAG_f^OHtvXsXJWXZC9x&faN1Q+V0gOvxvX