diff --git a/eos/db/saveddata/cart.py b/eos/db/saveddata/cart.py
new file mode 100644
index 0000000000..8e141176ff
--- /dev/null
+++ b/eos/db/saveddata/cart.py
@@ -0,0 +1,41 @@
+# ===============================================================================
+# 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, DateTime
+from sqlalchemy.orm import mapper, relation
+import datetime
+
+from eos.db import saveddata_meta
+from eos.saveddata.cargo import Cargo
+from eos.saveddata.fit import Fit
+
+cart_table = Table("cart", 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),
+ Column("created", DateTime, nullable=True, default=datetime.datetime.now),
+ Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now),
+ )
+
+mapper(Cargo, cart_table,
+ properties={
+ "owner": relation(Fit)
+ }
+)
diff --git a/eos/saveddata/cart.py b/eos/saveddata/cart.py
new file mode 100644
index 0000000000..917cdb61b4
--- /dev/null
+++ b/eos/saveddata/cart.py
@@ -0,0 +1,101 @@
+# ===============================================================================
+# 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 .
+# ===============================================================================
+
+import sys
+from logbook import Logger
+
+from sqlalchemy.orm import validates, reconstructor
+
+import eos.db
+from eos.effectHandlerHelpers import HandledItem
+from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut
+
+pyfalog = Logger(__name__)
+
+
+class Cart(HandledItem, ItemAttrShortcut):
+ def __init__(self, item):
+ """Initialize cart from the program"""
+ self.__item = item
+ self.itemID = item.ID if item is not None else None
+ self.amount = 0
+ self.__itemModifiedAttributes = ModifiedAttributeDict()
+ self.__itemModifiedAttributes.original = item.attributes
+ self.__itemModifiedAttributes.overrides = item.overrides
+
+ @reconstructor
+ def init(self):
+ """Initialize cart from the database and validate"""
+ self.__item = None
+
+ if self.itemID:
+ self.__item = eos.db.getItem(self.itemID)
+ if self.__item is None:
+ pyfalog.error("Item (id: {0}) does not exist", self.itemID)
+ return
+
+ self.__itemModifiedAttributes = ModifiedAttributeDict()
+ self.__itemModifiedAttributes.original = self.__item.attributes
+ self.__itemModifiedAttributes.overrides = self.__item.overrides
+
+ @property
+ def itemModifiedAttributes(self):
+ return self.__itemModifiedAttributes
+
+ @property
+ def isInvalid(self):
+ return self.__item is None
+
+ @property
+ def item(self):
+ return self.__item
+
+ def clear(self):
+ self.itemModifiedAttributes.clear()
+
+ @validates("fitID", "itemID", "amount")
+ def validator(self, key, val):
+ map = {
+ "fitID" : lambda _val: isinstance(_val, int),
+ "itemID": lambda _val: isinstance(_val, int),
+ "amount": lambda _val: isinstance(_val, int)
+ }
+
+ if key == "amount" and val > sys.maxsize:
+ val = sys.maxsize
+
+ if not map[key](val):
+ raise ValueError(str(val) + " is not a valid value for " + key)
+ else:
+ return val
+
+ def __deepcopy__(self, memo):
+ copy = Cart(self.item)
+ copy.amount = self.amount
+ return copy
+
+ def rebase(self, item):
+ amount = self.amount
+ Cart.__init__(self, item)
+ self.amount = amount
+
+ def __repr__(self):
+ return "Cart(ID={}, name={}) at {}".format(
+ self.item.ID, self.item.name, hex(id(self))
+ )
diff --git a/eos/saveddata/damagePattern.py b/eos/saveddata/damagePattern.py
index 6f3ec94322..b4bb12c140 100644
--- a/eos/saveddata/damagePattern.py
+++ b/eos/saveddata/damagePattern.py
@@ -59,38 +59,6 @@ def _c(x):
(-20, (_c(_t('Exotic Plasma')) + _t('Baryon'), 0, 59737, 0, 40263)),
(-21, (_c(_t('Exotic Plasma')) + _t('Tetryon'), 0, 69208, 0, 30792)),
(-22, (_c(_t('Exotic Plasma')) + '|' + _t('[T2] Occult'), 0, 55863, 0, 44137)),
- (-23, (_c(_t('Hybrid Charges')) + '|' + _t('[T2] Spike'), 0, 4, 4, 0)),
- (-24, (_c(_t('Hybrid Charges')) + '|' + _t('[T2] Null'), 0, 6, 5, 0)),
- (-25, (_c(_t('Hybrid Charges')) + _t('Iron'), 0, 2, 3, 0)),
- (-26, (_c(_t('Hybrid Charges')) + _t('Tungsten'), 0, 2, 4, 0)),
- (-27, (_c(_t('Hybrid Charges')) + _t('Iridium'), 0, 3, 4, 0)),
- (-28, (_c(_t('Hybrid Charges')) + _t('Lead'), 0, 3, 5, 0)),
- (-29, (_c(_t('Hybrid Charges')) + _t('Thorium'), 0, 4, 5, 0)),
- (-30, (_c(_t('Hybrid Charges')) + _t('Uranium'), 0, 4, 6, 0)),
- (-31, (_c(_t('Hybrid Charges')) + _t('Plutonium'), 0, 5, 6, 0)),
- (-32, (_c(_t('Hybrid Charges')) + _t('Antimatter'), 0, 5, 7, 0)),
- (-33, (_c(_t('Hybrid Charges')) + '|' + _t('[T2] Javelin'), 0, 8, 6, 0)),
- (-34, (_c(_t('Hybrid Charges')) + '|' + _t('[T2] Void'), 0, 7.7, 7.7, 0)),
- (-35, (_c(_t('Projectile Ammo')) + '|' + _t('[T2] Tremor'), 0, 0, 3, 5)),
- (-36, (_c(_t('Projectile Ammo')) + '|' + _t('[T2] Barrage'), 0, 0, 5, 6)),
- (-37, (_c(_t('Projectile Ammo')) + _t('Carbonized Lead'), 0, 0, 4, 1)),
- (-38, (_c(_t('Projectile Ammo')) + _t('Nuclear'), 0, 0, 1, 4)),
- (-39, (_c(_t('Projectile Ammo')) + _t('Proton'), 3, 0, 2, 0)),
- (-40, (_c(_t('Projectile Ammo')) + _t('Depleted Uranium'), 0, 3, 2, 3)),
- (-41, (_c(_t('Projectile Ammo')) + _t('Titanium Sabot'), 0, 0, 6, 2)),
- (-42, (_c(_t('Projectile Ammo')) + _t('EMP'), 9, 0, 1, 2)),
- (-43, (_c(_t('Projectile Ammo')) + _t('Phased Plasma'), 0, 10, 2, 0)),
- (-44, (_c(_t('Projectile Ammo')) + _t('Fusion'), 0, 0, 2, 10)),
- (-45, (_c(_t('Projectile Ammo')) + '|' + _t('[T2] Quake'), 0, 0, 5, 9)),
- (-46, (_c(_t('Projectile Ammo')) + '|' + _t('[T2] Hail'), 0, 0, 3.3, 12.1)),
- (-47, (_c(_t('Missiles')) + _t('Mjolnir'), 1, 0, 0, 0)),
- (-48, (_c(_t('Missiles')) + _t('Inferno'), 0, 1, 0, 0)),
- (-49, (_c(_t('Missiles')) + _t('Scourge'), 0, 0, 1, 0)),
- (-50, (_c(_t('Missiles')) + _t('Nova'), 0, 0, 0, 1)),
- (-51, (_c(_t('Bombs')) + _t('Electron Bomb'), 6400, 0, 0, 0)),
- (-52, (_c(_t('Bombs')) + _t('Scorch Bomb'), 0, 6400, 0, 0)),
- (-53, (_c(_t('Bombs')) + _t('Concussion Bomb'), 0, 0, 6400, 0)),
- (-54, (_c(_t('Bombs')) + _t('Shrapnel Bomb'), 0, 0, 0, 6400)),
# Source: ticket #2067 and #2265
(-55, (_c(_t('NPC')) + _c(_t('Abyssal')) + _t('All'), 126, 427, 218, 230)),
(-109, (_c(_t('NPC')) + _c(_t('Abyssal')) + _t('Angel'), 450, 72, 80, 398)),
diff --git a/eve.db b/eve.db
new file mode 100644
index 0000000000..3b357c781c
Binary files /dev/null and b/eve.db differ
diff --git a/gui/additionsPane.py b/gui/additionsPane.py
index c7c8eb3761..ee54789777 100644
--- a/gui/additionsPane.py
+++ b/gui/additionsPane.py
@@ -29,6 +29,7 @@
from gui.builtinAdditionPanes.fighterView import FighterView
from gui.builtinAdditionPanes.implantView import ImplantView
from gui.builtinAdditionPanes.notesView import NotesView
+from gui.builtinAdditionPanes.cartView import CartView
from gui.builtinAdditionPanes.projectedView import ProjectedView
from gui.chrome_tabs import ChromeNotebook
from gui.toggle_panel import TogglePanel
@@ -61,6 +62,7 @@ def __init__(self, parent, mainFrame):
gangImg = BitmapLoader.getImage("fleet_fc_small", "gui")
cargoImg = BitmapLoader.getImage("cargo_small", "gui")
notesImg = BitmapLoader.getImage("skill_small", "gui")
+ cartImg = BitmapLoader.getImage("cart_small", "gui")
self.drone = DroneView(self.notebook)
self.notebook.AddPage(self.drone, _t("Drones"), image=droneImg, closeable=False)
@@ -86,12 +88,16 @@ def __init__(self, parent, mainFrame):
self.notes = NotesView(self.notebook)
self.notebook.AddPage(self.notes, _t("Notes"), image=notesImg, closeable=False)
+
+ self.cart = CartView(self.notebook)
+ self.notebook.AddPage(self.cart, _t("Cart"), image=cartImg, closeable=False)
+
self.mainFrame.Bind(GE.FIT_CHANGED, self.OnFitChanged)
self.mainFrame.Bind(GE.FIT_NOTES_CHANGED, self.OnNotesChanged)
self.notebook.SetSelection(0)
- PANES = ["Drones", "Fighters", "Cargo", "Implants", "Boosters", "Projected", "Command", "Notes"]
+ PANES = ["Drones", "Fighters", "Cargo", "Implants", "Boosters", "Projected", "Command", "Notes", "Cart"]
def select(self, name, focus=True):
self.notebook.SetSelection(self.PANES.index(name), focus=focus)
diff --git a/gui/builtinAdditionPanes/cartView.py b/gui/builtinAdditionPanes/cartView.py
new file mode 100644
index 0000000000..501e165847
--- /dev/null
+++ b/gui/builtinAdditionPanes/cartView.py
@@ -0,0 +1,267 @@
+# =============================================================================
+# 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 .
+# =============================================================================
+
+# noinspection PyPackageRequirements
+import wx
+
+import gui.display as d
+import gui.fitCommands as cmd
+import gui.globalEvents as GE
+from gui.contextMenu import ContextMenu
+from gui.builtinMarketBrowser.events import ITEM_SELECTED, ItemSelected
+from gui.utils.staticHelpers import DragDropHelper
+from service.fit import Fit
+from service.market import Market
+from service.port import Port
+from service.port.esi import ESIExportException
+
+
+_t = wx.GetTranslation
+
+class CartViewDrop(wx.DropTarget):
+ def __init__(self, dropFn, *args, **kwargs):
+ super(CartViewDrop, self).__init__(*args, **kwargs)
+ self.dropFn = dropFn
+ #this is really transferring an EVE itemID
+ self.dropData = wx.TextDataObject()
+ self.SetDataObject(self.dropData)
+
+ def OnData(self, x, y, t):
+ if self.GetData():
+ dragged_data = DragDropHelper.data
+ data = dragged_data.split(':')
+ self.dropFn(x, y, data)
+ return t
+
+
+class CartView(d.Display):
+ DEFAULT_COLS = ["Base Icon",
+ "Base Name",
+ "attr:volume",
+ "Price"]
+
+ def __init__(self, parent):
+ d.Display.__init__(self, parent, style=wx.BORDER_NONE)
+
+ self.lastFitId = None
+
+ self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged)
+ self.mainFrame.Bind(ITEM_SELECTED, self.addItem)
+ self.Bind(wx.EVT_LEFT_DCLICK, self.onLeftDoubleClick)
+ self.Bind(wx.EVT_KEY_UP, self.kbEvent)
+ self.SetDropTarget(CartViewDrop(self.handleListDrag))
+ self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.startDrag)
+
+ self.Bind(wx.EVT_CONTEXT_MENU, self.spawnMenu)
+
+
+
+
+
+ def addItem(self, event):
+ newFit = "cartBackGroundFit.xml"
+ item = Market.getInstance().getItem(event.itemID, eager='group')
+ if item is None or not (item.isCharge or item.isCommodity):
+ event.Skip()
+ return
+
+ fitID = self.mainFrame.getActiveFit()
+ fit = Fit.getInstance().getFit(fitID)
+
+ if not fit:
+ event.Skip()
+ return
+ modifiers = wx.GetMouseState().GetModifiers()
+ amount = 1
+ if modifiers == wx.MOD_CONTROL:
+ amount = 10
+ elif modifiers == wx.MOD_ALT:
+ amount = 100
+ elif modifiers == wx.MOD_CONTROL | wx.MOD_ALT:
+ amount = 1000
+ self.mainFrame.command.Submit(cmd.GuiAddCartCommand(
+ fitID=fitID, itemID=item.ID, amount=amount))
+ self.mainFrame.additionsPane.select('Cart')
+ event.Skip()
+ def handleListDrag(self, x, y, data):
+ """
+ Handles dragging of items from various pyfa displays which support it
+
+ data is list with two indices:
+ data[0] is hard-coded str of originating source
+ data[1] is typeID or index of data we want to manipulate
+ """
+
+ if data[0] == "fitting":
+ self.swapModule(x, y, int(data[1]))
+ elif data[0] == "market":
+ fitID = self.mainFrame.getActiveFit()
+ if fitID:
+ self.mainFrame.command.Submit(cmd.GuiAddCartCommand(
+ fitID=fitID, itemID=int(data[1]), amount=1))
+
+ def startDrag(self, event):
+ row = event.GetIndex()
+
+ if row != -1:
+ data = wx.TextDataObject()
+ try:
+ dataStr = "cart:{}".format(self.cart[row].itemID)
+ except IndexError:
+ return
+ data.SetText(dataStr)
+
+ self.unselectAll()
+ self.Select(row, True)
+
+ dropSource = wx.DropSource(self)
+ dropSource.SetData(data)
+ DragDropHelper.data = dataStr
+ dropSource.DoDragDrop()
+
+ def kbEvent(self, event):
+ keycode = event.GetKeyCode()
+ modifiers = event.GetModifiers()
+ if keycode == wx.WXK_ESCAPE and modifiers == wx.MOD_NONE:
+ self.unselectAll()
+ elif keycode == 65 and modifiers == wx.MOD_CONTROL:
+ self.selectAll()
+ elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and modifiers == wx.MOD_NONE:
+ carts = self.getSelectedCarts()
+ self.removeCarts(carts)
+ event.Skip()
+
+ def swapModule(self, x, y, modIdx):
+ """Swap a module from fitting window with carts"""
+ sFit = Fit.getInstance()
+ fit = sFit.getFit(self.mainFrame.getActiveFit())
+ dstRow, _ = self.HitTest((x, y))
+
+ if dstRow > -1:
+ try:
+ dstCartsItemID = getattr(self.cart[dstRow], 'itemID', None)
+ except IndexError:
+ dstCartsItemID = None
+ else:
+ dstCartsItemID = None
+
+ self.mainFrame.command.Submit(cmd.GuiLocalModuleToCartCommand(
+ fitID=self.mainFrame.getActiveFit(),
+ modPosition=modIdx,
+ cartItemID=dstCartsItemID,
+ copy=wx.GetMouseState().GetModifiers() == wx.MOD_CONTROL))
+
+ def fitChanged(self, event):
+ event.Skip()
+ activeFitID = self.mainFrame.getActiveFit()
+ if activeFitID is not None and activeFitID not in event.fitIDs:
+ return
+
+ sFit = Fit.getInstance()
+ fit = sFit.getFit(activeFitID)
+
+ # self.Parent.Parent.DisablePage(self, not fit or fit.isStructure)
+
+ # Clear list and get out if current fitId is None
+ if activeFitID is None and self.lastFitId is not None:
+ self.DeleteAllItems()
+ self.lastFitId = None
+ return
+
+ self.original = fit.cart if fit is not None else None
+ self.cart = fit.cart[:] if fit is not None else None
+ if self.cart is not None:
+ self.cart.sort(key=lambda c: (c.item.group.category.name, c.item.group.name, c.item.name))
+
+ if activeFitID != self.lastFitId:
+ self.lastFitId = activeFitID
+
+ item = self.GetNextItem(-1, wx.LIST_NEXT_ALL, wx.LIST_STATE_DONTCARE)
+
+ if item != -1:
+ self.EnsureVisible(item)
+
+ self.unselectAll()
+
+ self.populate(self.cart)
+ self.refresh(self.cart)
+
+ def onLeftDoubleClick(self, event):
+ row, _ = self.HitTest(event.Position)
+ if row != -1:
+ try:
+ cart = self.cart[row]
+ except IndexError:
+ return
+ self.removeCarts([cart])
+
+ def removeCarts(self, carts):
+ fitID = self.mainFrame.getActiveFit()
+ itemIDs = []
+ for cart in carts:
+ if cart in self.original:
+ itemIDs.append(cart.itemID)
+ self.mainFrame.command.Submit(cmd.GuiRemoveCartCommand(fitID=fitID, itemIDs=itemIDs))
+
+ def spawnMenu(self, event):
+ clickedPos = self.getRowByAbs(event.Position)
+ self.ensureSelection(clickedPos)
+
+ selection = self.getSelectedCarts()
+ mainCart = None
+ if clickedPos != -1:
+ try:
+ cart = self.cart[clickedPos]
+ except IndexError:
+ pass
+ else:
+ if cart in self.original:
+ mainCart = cart
+ itemContext = None if mainCart is None else Market.getInstance().getCategoryByItem(
+ mainCart.item).displayName
+ menu = ContextMenu.getMenu(self, mainCart, selection, ("cartItem", itemContext),
+ ("cartItemMisc", itemContext))
+ if menu:
+ self.PopupMenu(menu)
+
+ def getSelectedCarts(self):
+ carts = []
+ for row in self.getSelectedRows():
+ try:
+ cart = self.cart[row]
+ except IndexError:
+ continue
+ carts.append(cart)
+ return carts
+
+ def getTabExtraText(self):
+ fitID = self.mainFrame.getActiveFit()
+ if fitID is None:
+ return None
+ sFit = Fit.getInstance()
+ fit = sFit.getFit(fitID)
+ if fit is None:
+ return None
+ opt = sFit.serviceFittingOptions["additionsLabels"]
+ # Total amount of cart items
+ if opt in (1, 2):
+ amount = len(fit.cart)
+ return ' ({})'.format(amount) if amount else None
+ else:
+ return None
diff --git a/gui/builtinContextMenus/additionsImport.py b/gui/builtinContextMenus/additionsImport.py
index bafda6a59c..1b5e753982 100644
--- a/gui/builtinContextMenus/additionsImport.py
+++ b/gui/builtinContextMenus/additionsImport.py
@@ -21,7 +21,8 @@ def __init__(self):
'cargoItemMisc': (_t('Cargo Items'), lambda i: not i.isAbyssal, cmd.GuiImportCargosCommand),
'implantItemMisc': (_t('Implants'), lambda i: i.isImplant, cmd.GuiImportImplantsCommand),
'implantItemMiscChar': (_t('Implants'), lambda i: i.isImplant, cmd.GuiImportImplantsCommand),
- 'boosterItemMisc': (_t('Boosters'), lambda i: i.isBooster, cmd.GuiImportBoostersCommand)
+ 'boosterItemMisc': (_t('Boosters'), lambda i: i.isBooster, cmd.GuiImportBoostersCommand),
+ 'cartItemMisc':(_t('Cart'), lambda i: i.isCart, cmd.GuiImportCartCommand)
}
def display(self, callingWindow, srcContext):
diff --git a/gui/builtinContextMenus/cartAdd.py b/gui/builtinContextMenus/cartAdd.py
new file mode 100644
index 0000000000..0ca05a0146
--- /dev/null
+++ b/gui/builtinContextMenus/cartAdd.py
@@ -0,0 +1,43 @@
+import wx
+
+import gui.fitCommands as cmd
+import gui.mainFrame
+from gui.contextMenu import ContextMenuSingle
+from service.fit import Fit
+
+_t = wx.GetTranslation
+
+
+class AddToCart(ContextMenuSingle):
+
+ def __init__(self):
+ self.mainFrame = gui.mainFrame.MainFrame.getInstance()
+
+ def display(self, callingWindow, srcContext, mainItem):
+ if srcContext not in ("marketItemGroup", "marketItemMisc"):
+ return False
+
+ if mainItem is None:
+ return False
+
+ sFit = Fit.getInstance()
+ fitID = self.mainFrame.getActiveFit()
+ fit = sFit.getFit(fitID)
+
+ if not fit or (fit.isStructure and mainItem.category.ID != 8):
+ return False
+
+ return True
+
+ def getText(self, callingWindow, itmContext, mainItem):
+ return _t("Add {} to Cart").format(itmContext)
+
+ def activate(self, callingWindow, fullContext, mainItem, i):
+ fitID = self.mainFrame.getActiveFit()
+ typeID = int(mainItem.ID)
+ command = cmd.GuiAddCartCommand(fitID=fitID, itemID=typeID, amount=1)
+ if self.mainFrame.command.Submit(command):
+ self.mainFrame.additionsPane.select("Cart", focus=False)
+
+
+AddToCart.register()
diff --git a/gui/builtinContextMenus/cartAddAmmo.py b/gui/builtinContextMenus/cartAddAmmo.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/gui/fitCommands/__init__.py b/gui/fitCommands/__init__.py
index ab56b1ddcd..de46813dd0 100644
--- a/gui/fitCommands/__init__.py
+++ b/gui/fitCommands/__init__.py
@@ -9,6 +9,19 @@
from .gui.cargo.changeMetas import GuiChangeCargoMetasCommand
from .gui.cargo.imprt import GuiImportCargosCommand
from .gui.cargo.remove import GuiRemoveCargosCommand
+
+from .gui.cart.add import GuiAddCartCommand
+from .gui.cart.changeAmount import GuiChangeCartsAmountCommand
+from .gui.cart.changeMetas import GuiChangeCartMetasCommand
+from .gui.cart.imprt import GuiImportCartCommand
+from .gui.cart.remove import GuiRemoveCartCommand
+
+
+
+
+
+
+
from .gui.commandFit.add import GuiAddCommandFitsCommand
from .gui.commandFit.remove import GuiRemoveCommandFitsCommand
from .gui.commandFit.toggleStates import GuiToggleCommandFitStatesCommand
@@ -60,6 +73,13 @@
from .gui.localModule.swap import GuiSwapLocalModulesCommand
from .gui.localModuleCargo.cargoToLocalModule import GuiCargoToLocalModuleCommand
from .gui.localModuleCargo.localModuleToCargo import GuiLocalModuleToCargoCommand
+
+
+from .gui.localModuleCart.cartToLocalModule import GuiCartToLocalModuleCommand
+from .gui.localModuleCart.localModuleToCart import GuiLocalModuleToCartCommand
+
+
+
from .gui.projectedChangeProjectionRange import GuiChangeProjectedItemsProjectionRangeCommand
from .gui.projectedChangeStates import GuiChangeProjectedItemStatesCommand
from .gui.projectedDrone.add import GuiAddProjectedDroneCommand
diff --git a/gui/fitCommands/calc/cart/__init__.py b/gui/fitCommands/calc/cart/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/gui/fitCommands/calc/cart/add.py b/gui/fitCommands/calc/cart/add.py
new file mode 100644
index 0000000000..c2b75c6064
--- /dev/null
+++ b/gui/fitCommands/calc/cart/add.py
@@ -0,0 +1,35 @@
+import wx
+from logbook import Logger
+
+from service.fit import Fit
+
+
+pyfalog = Logger(__name__)
+
+
+class CalcAddCartCommand(wx.Command):
+
+ def __init__(self, fitID, cartInfo):
+ wx.Command.__init__(self, True, 'Add Cart')
+ self.fitID = fitID
+ self.cartInfo = cartInfo
+
+ def Do(self):
+ pyfalog.debug('Doing addition of cart {} to fit {}'.format(self.cartInfo, self.fitID))
+ fit = Fit.getInstance().getFit(self.fitID)
+ cart = next((c for c in fit.cart if c.itemID == self.cartInfo.itemID), None)
+ if cart is not None:
+ cart.amount += self.cartInfo.amount
+ else:
+ cart = self.cartInfo.toCart()
+ fit.cart.append(cart)
+ if cart not in fit.cart:
+ pyfalog.warning('Failed to append to list')
+ return False
+ return True
+
+ def Undo(self):
+ pyfalog.debug('Undoing addition of cart {} to fit {}'.format(self.cartInfo, self.fitID))
+ from .remove import CalcRemoveCartCommand
+ cmd = CalcRemoveCartCommand(fitID=self.fitID, cartInfo=self.cartInfo)
+ return cmd.Do()
diff --git a/gui/fitCommands/calc/cart/changeAmount.py b/gui/fitCommands/calc/cart/changeAmount.py
new file mode 100644
index 0000000000..ecc4799b57
--- /dev/null
+++ b/gui/fitCommands/calc/cart/changeAmount.py
@@ -0,0 +1,35 @@
+import wx
+from logbook import Logger
+
+from gui.fitCommands.helpers import CartInfo
+from service.fit import Fit
+
+
+pyfalog = Logger(__name__)
+
+
+class CalcChangeCartAmountCommand(wx.Command):
+
+ def __init__(self, fitID, cartInfo):
+ wx.Command.__init__(self, True, 'Change Cart Amount')
+ self.fitID = fitID
+ self.cartInfo = cartInfo
+ self.savedCartInfo = None
+
+ def Do(self):
+ pyfalog.debug('Doing change of cart {} for fit {}'.format(self.cartInfo, self.fitID))
+ fit = Fit.getInstance().getFit(self.fitID)
+ cart = next((c for c in fit.cart if c.itemID == self.cartInfo.itemID), None)
+ if cart is None:
+ pyfalog.warning('Cannot find cart item')
+ return False
+ self.savedCartInfo = CartInfo.fromCart(cart)
+ if self.cartInfo.amount == self.savedCartInfo.amount:
+ return False
+ cart.amount = self.cartInfo.amount
+ return True
+
+ def Undo(self):
+ pyfalog.debug('Undoing change of cart {} for fit {}'.format(self.cartInfo, self.fitID))
+ cmd = CalcChangeCartAmountCommand(fitID=self.fitID, cartInfo=self.savedCartInfo)
+ return cmd.Do()
diff --git a/gui/fitCommands/calc/cart/remove.py b/gui/fitCommands/calc/cart/remove.py
new file mode 100644
index 0000000000..65dc1ce5c9
--- /dev/null
+++ b/gui/fitCommands/calc/cart/remove.py
@@ -0,0 +1,37 @@
+import wx
+from logbook import Logger
+
+from gui.fitCommands.helpers import CartInfo
+from service.fit import Fit
+
+
+pyfalog = Logger(__name__)
+
+
+class CalcRemoveCartCommand(wx.Command):
+
+ def __init__(self, fitID, cartInfo):
+ wx.Command.__init__(self, True, 'Remove Cart')
+ self.fitID = fitID
+ self.cartInfo = cartInfo
+ self.savedRemovedAmount = None
+
+ def Do(self):
+ pyfalog.debug('Doing removal of cart {} to fit {}'.format(self.cartInfo, self.fitID))
+ fit = Fit.getInstance().getFit(self.fitID)
+ cart = next((x for x in fit.cart if x.itemID == self.cartInfo.itemID), None)
+ if cart is None:
+ return False
+ self.savedRemovedAmount = min(cart.amount, self.cartInfo.amount)
+ cart.amount -= self.savedRemovedAmount
+ if cart.amount <= 0:
+ fit.cart.remove(cart)
+ return True
+
+ def Undo(self):
+ pyfalog.debug('Undoing removal of cart {} to fit {}'.format(self.cartInfo, self.fitID))
+ from .add import CalcAddCartCommand
+ cmd = CalcAddCartCommand(
+ fitID=self.fitID,
+ cartInfo=CartInfo(itemID=self.cartInfo.itemID, amount=self.savedRemovedAmount))
+ return cmd.Do()
diff --git a/gui/fitCommands/gui/__init__.py b/gui/fitCommands/gui/__init__.py
index e69de29bb2..8b13789179 100644
--- a/gui/fitCommands/gui/__init__.py
+++ b/gui/fitCommands/gui/__init__.py
@@ -0,0 +1 @@
+
diff --git a/gui/fitCommands/gui/cart/__init__.py b/gui/fitCommands/gui/cart/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/gui/fitCommands/gui/cart/add.py b/gui/fitCommands/gui/cart/add.py
new file mode 100644
index 0000000000..1946713f59
--- /dev/null
+++ b/gui/fitCommands/gui/cart/add.py
@@ -0,0 +1,32 @@
+import wx
+
+import eos.db
+import gui.mainFrame
+from gui import globalEvents as GE
+from gui.fitCommands.calc.cart.add import CalcAddCartCommand
+from gui.fitCommands.helpers import CargoInfo, InternalCommandHistory
+from service.market import Market
+
+
+class GuiAddCartCommand(wx.Command):
+
+ def __init__(self, fitID, itemID, amount):
+ wx.Command.__init__(self, True, 'Add Cart')
+ self.internalHistory = InternalCommandHistory()
+ self.fitID = fitID
+ self.itemID = itemID
+ self.amount = amount
+
+ def Do(self):
+ cmd = CalcAddCartCommand(fitID=self.fitID, cartInfo=CargoInfo(itemID=self.itemID, amount=self.amount))
+ success = self.internalHistory.submit(cmd)
+ Market.getInstance().storeRecentlyUsed(self.itemID)
+ eos.db.commit()
+ wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,)))
+ return success
+
+ def Undo(self):
+ success = self.internalHistory.undoAll()
+ eos.db.commit()
+ wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,)))
+ return success
diff --git a/gui/fitCommands/gui/cart/changeAmount.py b/gui/fitCommands/gui/cart/changeAmount.py
new file mode 100644
index 0000000000..5afc197f92
--- /dev/null
+++ b/gui/fitCommands/gui/cart/changeAmount.py
@@ -0,0 +1,41 @@
+import math
+
+import wx
+
+import eos.db
+import gui.mainFrame
+from gui import globalEvents as GE
+from gui.fitCommands.calc.cart.changeAmount import CalcChangeCartAmountCommand
+from gui.fitCommands.calc.cart.remove import CalcRemoveCartCommand
+from gui.fitCommands.helpers import CartInfo, InternalCommandHistory
+
+
+class GuiChangeCartsAmountCommand(wx.Command):
+
+ def __init__(self, fitID, itemIDs, amount):
+ wx.Command.__init__(self, True, 'Change Cargo Amount')
+ self.internalHistory = InternalCommandHistory()
+ self.fitID = fitID
+ self.itemIDs = itemIDs
+ self.amount = amount
+
+ def Do(self):
+ results = []
+ if self.amount > 0:
+ for itemID in self.itemIDs:
+ cmd = CalcChangeCartAmountCommand(fitID=self.fitID, cartInfo=CartInfo(itemID=itemID, amount=self.amount))
+ results.append(self.internalHistory.submit(cmd))
+ else:
+ for itemID in self.itemIDs:
+ cmd = CalcRemoveCartCommand(fitID=self.fitID, cartInfo=CartInfo(itemID=itemID, amount=math.inf))
+ results.append(self.internalHistory.submit(cmd))
+ success = any(results)
+ eos.db.commit()
+ wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,)))
+ return success
+
+ def Undo(self):
+ success = self.internalHistory.undoAll()
+ eos.db.commit()
+ wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,)))
+ return success
diff --git a/gui/fitCommands/gui/cart/changeMetas.py b/gui/fitCommands/gui/cart/changeMetas.py
new file mode 100644
index 0000000000..cdc039d602
--- /dev/null
+++ b/gui/fitCommands/gui/cart/changeMetas.py
@@ -0,0 +1,49 @@
+import math
+import wx
+
+import eos.db
+import gui.mainFrame
+from gui import globalEvents as GE
+from gui.fitCommands.calc.cart.add import CalcAddCartCommand
+from gui.fitCommands.calc.cart.remove import CalcRemoveCartCommand
+from gui.fitCommands.helpers import CartInfo, InternalCommandHistory
+from service.fit import Fit
+
+
+class GuiChangeCartMetasCommand(wx.Command):
+
+ def __init__(self, fitID, itemIDs, newItemID):
+ wx.Command.__init__(self, True, 'Change Cart Metas')
+ self.internalHistory = InternalCommandHistory()
+ self.fitID = fitID
+ self.itemIDs = itemIDs
+ self.newItemID = newItemID
+
+ def Do(self):
+ sFit = Fit.getInstance()
+ fit = sFit.getFit(self.fitID)
+ results = []
+ for itemID in self.itemIDs:
+ if itemID == self.newItemID:
+ continue
+ cargo = next((c for c in fit.cargo if c.itemID == itemID), None)
+ if cargo is None:
+ continue
+ amount = cargo.amount
+ cmdRemove = CalcRemoveCartCommand(
+ fitID=self.fitID,
+ cartInfo=CartInfo(itemID=itemID, amount=math.inf))
+ cmdAdd = CalcAddCartCommand(
+ fitID=self.fitID,
+ cartInfo=CartInfo(itemID=self.newItemID, amount=amount))
+ results.append(self.internalHistory.submitBatch(cmdRemove, cmdAdd))
+ success = any(results)
+ eos.db.commit()
+ wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,)))
+ return success
+
+ def Undo(self):
+ success = self.internalHistory.undoAll()
+ eos.db.commit()
+ wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,)))
+ return success
diff --git a/gui/fitCommands/gui/cart/imprt.py b/gui/fitCommands/gui/cart/imprt.py
new file mode 100644
index 0000000000..559562eb7e
--- /dev/null
+++ b/gui/fitCommands/gui/cart/imprt.py
@@ -0,0 +1,36 @@
+import wx
+
+import eos.db
+import gui.mainFrame
+from gui import globalEvents as GE
+from gui.fitCommands.calc.cart.add import CalcAddCartCommand
+from gui.fitCommands.helpers import CartInfo, InternalCommandHistory
+
+
+class GuiImportCartCommand(wx.Command):
+
+ def __init__(self, fitID, cargos):
+ wx.Command.__init__(self, True, 'Import Carts')
+ self.internalHistory = InternalCommandHistory()
+ self.fitID = fitID
+ self.cargos = {}
+ for itemID, amount, mutation in cargos:
+ if itemID not in self.cargos:
+ self.cargos[itemID] = 0
+ self.cargos[itemID] += amount
+
+ def Do(self):
+ results = []
+ for itemID, amount in self.cargos.items():
+ cmd = CalcAddCartCommand(fitID=self.fitID, cartInfo=CartInfo(itemID=itemID, amount=amount))
+ results.append(self.internalHistory.submit(cmd))
+ success = any(results)
+ eos.db.commit()
+ wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,)))
+ return success
+
+ def Undo(self):
+ success = self.internalHistory.undoAll()
+ eos.db.commit()
+ wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,)))
+ return success
diff --git a/gui/fitCommands/gui/cart/remove.py b/gui/fitCommands/gui/cart/remove.py
new file mode 100644
index 0000000000..968f4efc3b
--- /dev/null
+++ b/gui/fitCommands/gui/cart/remove.py
@@ -0,0 +1,39 @@
+import math
+
+import wx
+
+import eos.db
+import gui.mainFrame
+from gui import globalEvents as GE
+from gui.fitCommands.calc.cart.remove import CalcRemoveCartCommand
+from gui.fitCommands.helpers import CartInfo, InternalCommandHistory
+from service.market import Market
+
+
+class GuiRemoveCartCommand(wx.Command):
+
+ def __init__(self, fitID, itemIDs):
+ wx.Command.__init__(self, True, 'Remove Carts')
+ self.internalHistory = InternalCommandHistory()
+ self.fitID = fitID
+ self.itemIDs = itemIDs
+
+ def Do(self):
+ sMkt = Market.getInstance()
+ results = []
+ for itemID in self.itemIDs:
+ cmd = CalcRemoveCartCommand(
+ fitID=self.fitID,
+ cartInfo=CartInfo(itemID=itemID, amount=math.inf))
+ results.append(self.internalHistory.submit(cmd))
+ sMkt.storeRecentlyUsed(itemID)
+ success = any(results)
+ eos.db.commit()
+ wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,)))
+ return success
+
+ def Undo(self):
+ success = self.internalHistory.undoAll()
+ eos.db.commit()
+ wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,)))
+ return success
diff --git a/gui/fitCommands/gui/helpers.py b/gui/fitCommands/gui/helpers.py
new file mode 100644
index 0000000000..133aad11fb
--- /dev/null
+++ b/gui/fitCommands/gui/helpers.py
@@ -0,0 +1,591 @@
+import math
+
+import wx
+from logbook import Logger
+
+import eos.db
+from eos.const import FittingModuleState
+from eos.saveddata.booster import Booster
+from eos.saveddata.cargo import Cargo
+
+from eos.saveddata.cart import Cart
+
+from eos.saveddata.drone import Drone
+from eos.saveddata.fighter import Fighter
+from eos.saveddata.implant import Implant
+from eos.saveddata.module import Module
+from service.market import Market
+from utils.repr import makeReprStr
+
+
+pyfalog = Logger(__name__)
+
+
+class InternalCommandHistory:
+
+ def __init__(self):
+ self.__buffer = wx.CommandProcessor()
+
+ def submit(self, command):
+ return self.__buffer.Submit(command)
+
+ def submitBatch(self, *commands):
+ for command in commands:
+ if not self.__buffer.Submit(command):
+ # Undo what we already submitted
+ for commandToUndo in reversed(self.__buffer.Commands):
+ if commandToUndo in commands:
+ self.__buffer.Undo()
+ return False
+ return True
+
+ def undoAll(self):
+ undoneCommands = []
+ # Undo commands one by one, starting from the last
+ for commandToUndo in reversed(self.__buffer.Commands):
+ if commandToUndo.Undo():
+ undoneCommands.append(commandToUndo)
+ # If undoing fails, redo already undone commands, starting from the last undone
+ else:
+ for commandToRedo in reversed(undoneCommands):
+ if not commandToRedo.Do():
+ break
+ self.__buffer.ClearCommands()
+ return False
+ self.__buffer.ClearCommands()
+ return True
+
+ def __len__(self):
+ return len(self.__buffer.Commands)
+
+
+class ModuleInfo:
+
+ def __init__(
+ self, itemID, baseItemID=None, mutaplasmidID=None, mutations=None, chargeID=None,
+ state=None, spoolType=None, spoolAmount=None, rahPattern=None):
+ self.itemID = itemID
+ self.baseItemID = baseItemID
+ self.mutaplasmidID = mutaplasmidID
+ self.mutations = mutations
+ self.chargeID = chargeID
+ self.state = state
+ self.spoolType = spoolType
+ self.spoolAmount = spoolAmount
+ self.rahPattern = rahPattern
+
+ @classmethod
+ def fromModule(cls, mod, unmutate=False):
+ if mod is None:
+ return None
+ if unmutate and mod.isMutated:
+ info = cls(
+ itemID=mod.baseItemID,
+ baseItemID=None,
+ mutaplasmidID=None,
+ mutations={},
+ chargeID=mod.chargeID,
+ state=mod.state,
+ spoolType=mod.spoolType,
+ spoolAmount=mod.spoolAmount,
+ rahPattern=mod.rahPatternOverride)
+ else:
+ info = cls(
+ itemID=mod.itemID,
+ baseItemID=mod.baseItemID,
+ mutaplasmidID=mod.mutaplasmidID,
+ mutations={m.attrID: m.value for m in mod.mutators.values()},
+ chargeID=mod.chargeID,
+ state=mod.state,
+ spoolType=mod.spoolType,
+ spoolAmount=mod.spoolAmount,
+ rahPattern=mod.rahPatternOverride)
+ return info
+
+ def toModule(self, fallbackState=None):
+ mkt = Market.getInstance()
+
+ item = mkt.getItem(self.itemID, eager=('attributes', 'group.category'))
+ if self.baseItemID and self.mutaplasmidID:
+ baseItem = mkt.getItem(self.baseItemID, eager=('attributes', 'group.category'))
+ mutaplasmid = eos.db.getDynamicItem(self.mutaplasmidID)
+ else:
+ baseItem = None
+ mutaplasmid = None
+ try:
+ mod = Module(item, baseItem=baseItem, mutaplasmid=mutaplasmid)
+ except ValueError:
+ pyfalog.warning('Invalid item: {}'.format(self.itemID))
+ return None
+
+ if self.mutations is not None:
+ for attrID, mutator in mod.mutators.items():
+ if attrID in self.mutations:
+ mutator.value = self.mutations[attrID]
+
+ if self.spoolType is not None and self.spoolAmount is not None:
+ mod.spoolType = self.spoolType
+ mod.spoolAmount = self.spoolAmount
+
+ mod.rahPatternOverride = self.rahPattern
+
+ if self.state is not None:
+ if mod.isValidState(self.state):
+ mod.state = self.state
+ else:
+ mod.state = mod.getMaxState(proposedState=self.state)
+ elif fallbackState is not None:
+ if mod.isValidState(fallbackState):
+ mod.state = fallbackState
+
+ if self.chargeID is not None:
+ charge = mkt.getItem(self.chargeID, eager=('attributes',))
+ if charge is None:
+ pyfalog.warning('Cannot set charge {}'.format(self.chargeID))
+ return None
+ mod.charge = charge
+
+ return mod
+
+ def __eq__(self, other):
+ if not isinstance(other, ModuleInfo):
+ return False
+ return all((
+ self.itemID == other.itemID,
+ self.baseItemID == other.baseItemID,
+ self.mutaplasmidID == other.mutaplasmidID,
+ self.mutations == other.mutations,
+ self.chargeID == other.chargeID,
+ self.state == other.state,
+ self.spoolType == other.spoolType,
+ self.spoolAmount == other.spoolAmount,
+ self.rahPattern == other.rahPattern))
+
+ def __repr__(self):
+ return makeReprStr(self, [
+ 'itemID', 'baseItemID', 'mutaplasmidID', 'mutations',
+ 'chargeID', 'state', 'spoolType', 'spoolAmount', 'rahPattern'])
+
+
+class DroneInfo:
+
+ def __init__(self, amount, amountActive, itemID, baseItemID=None, mutaplasmidID=None, mutations=None):
+ self.itemID = itemID
+ self.baseItemID = baseItemID
+ self.mutaplasmidID = mutaplasmidID
+ self.mutations = mutations
+ self.amount = amount
+ self.amountActive = amountActive
+
+ @classmethod
+ def fromDrone(cls, drone):
+ if drone is None:
+ return None
+ info = cls(
+ itemID=drone.itemID,
+ amount=drone.amount,
+ amountActive=drone.amountActive,
+ baseItemID=drone.baseItemID,
+ mutaplasmidID=drone.mutaplasmidID,
+ mutations={m.attrID: m.value for m in drone.mutators.values()})
+ return info
+
+ def toDrone(self):
+ mkt = Market.getInstance()
+ item = mkt.getItem(self.itemID, eager=('attributes', 'group.category'))
+ if self.baseItemID and self.mutaplasmidID:
+ baseItem = mkt.getItem(self.baseItemID, eager=('attributes', 'group.category'))
+ mutaplasmid = eos.db.getDynamicItem(self.mutaplasmidID)
+ else:
+ baseItem = None
+ mutaplasmid = None
+ try:
+ drone = Drone(item, baseItem=baseItem, mutaplasmid=mutaplasmid)
+ except ValueError:
+ pyfalog.warning('Invalid item: {}'.format(self.itemID))
+ return None
+
+ if self.mutations is not None:
+ for attrID, mutator in drone.mutators.items():
+ if attrID in self.mutations:
+ mutator.value = self.mutations[attrID]
+
+ drone.amount = self.amount
+ drone.amountActive = self.amountActive
+ return drone
+
+ def __repr__(self):
+ return makeReprStr(self, [
+ 'itemID', 'amount', 'amountActive',
+ 'baseItemID', 'mutaplasmidID', 'mutations'])
+
+
+class FighterInfo:
+
+ def __init__(self, itemID, amount=None, state=None, abilities=None):
+ self.itemID = itemID
+ self.amount = amount
+ self.state = state
+ self.abilities = abilities
+
+ @classmethod
+ def fromFighter(cls, fighter):
+ if fighter is None:
+ return None
+ info = cls(
+ itemID=fighter.itemID,
+ amount=fighter.amount,
+ state=fighter.active,
+ abilities={fa.effectID: fa.active for fa in fighter.abilities})
+ return info
+
+ def toFighter(self):
+ item = Market.getInstance().getItem(self.itemID, eager=('attributes', 'group.category'))
+ try:
+ fighter = Fighter(item)
+ except ValueError:
+ pyfalog.warning('Invalid item: {}'.format(self.itemID))
+ return None
+ if self.amount is not None:
+ fighter.amount = self.amount
+ if self.state is not None:
+ fighter.active = self.state
+ if self.abilities is not None:
+ for ability in fighter.abilities:
+ ability.active = self.abilities.get(ability.effectID, ability.active)
+ return fighter
+
+ def __repr__(self):
+ return makeReprStr(self, ['itemID', 'amount', 'state', 'abilities'])
+
+
+class ImplantInfo:
+
+ def __init__(self, itemID, state=None):
+ self.itemID = itemID
+ self.state = state
+
+ @classmethod
+ def fromImplant(cls, implant):
+ if implant is None:
+ return None
+ info = cls(
+ itemID=implant.itemID,
+ state=implant.active)
+ return info
+
+ def toImplant(self):
+ item = Market.getInstance().getItem(self.itemID, eager=('attributes', 'group.category'))
+ try:
+ implant = Implant(item)
+ except ValueError:
+ pyfalog.warning('Invalid item: {}'.format(self.itemID))
+ return None
+ if self.state is not None:
+ implant.active = self.state
+ return implant
+
+ def __repr__(self):
+ return makeReprStr(self, ['itemID', 'state'])
+
+
+class BoosterInfo:
+
+ def __init__(self, itemID, state=None, sideEffects=None):
+ self.itemID = itemID
+ self.state = state
+ self.sideEffects = sideEffects
+
+ @classmethod
+ def fromBooster(cls, booster):
+ if booster is None:
+ return None
+ info = cls(
+ itemID=booster.itemID,
+ state=booster.active,
+ sideEffects={se.effectID: se.active for se in booster.sideEffects})
+ return info
+
+ def toBooster(self):
+ item = Market.getInstance().getItem(self.itemID, eager=('attributes', 'group.category'))
+ try:
+ booster = Booster(item)
+ except ValueError:
+ pyfalog.warning('Invalid item: {}'.format(self.itemID))
+ return None
+ if self.state is not None:
+ booster.active = self.state
+ if self.sideEffects is not None:
+ for sideEffect in booster.sideEffects:
+ sideEffect.active = self.sideEffects.get(sideEffect.effectID, sideEffect.active)
+ return booster
+
+ def __repr__(self):
+ return makeReprStr(self, ['itemID', 'state', 'sideEffects'])
+
+
+class CargoInfo:
+
+ def __init__(self, itemID, amount):
+ self.itemID = itemID
+ self.amount = amount
+
+ @classmethod
+ def fromCargo(cls, cargo):
+ if cargo is None:
+ return None
+ info = cls(
+ itemID=cargo.itemID,
+ amount=cargo.amount)
+ return info
+
+ def toCargo(self):
+ item = Market.getInstance().getItem(self.itemID)
+ cargo = Cargo(item)
+ cargo.amount = self.amount
+ return cargo
+
+ def __repr__(self):
+ return makeReprStr(self, ['itemID', 'amount'])
+
+
+def activeStateLimit(itemIdentity):
+ item = Market.getInstance().getItem(itemIdentity)
+ if {
+ 'moduleBonusAssaultDamageControl', 'moduleBonusIndustrialInvulnerability',
+ 'microJumpDrive', 'microJumpPortalDrive', 'emergencyHullEnergizer',
+ 'cynosuralGeneration', 'jumpPortalGeneration', 'jumpPortalGenerationBO',
+ 'cloneJumpAccepting', 'cloakingWarpSafe', 'cloakingPrototype', 'cloaking',
+ 'massEntanglerEffect5', 'electronicAttributeModifyOnline', 'targetPassively',
+ 'cargoScan', 'shipScan', 'surveyScan', 'targetSpectrumBreakerBonus',
+ 'interdictionNullifierBonus', 'warpCoreStabilizerActive',
+ 'industrialItemCompression'
+ }.intersection(item.effects):
+ return FittingModuleState.ONLINE
+ return FittingModuleState.ACTIVE
+
+
+def droneStackLimit(fit, itemIdentity):
+ item = Market.getInstance().getItem(itemIdentity)
+ hardLimit = max(5, fit.extraAttributes["maxActiveDrones"])
+ releaseLimit = fit.getReleaseLimitForDrone(item)
+ limit = min(hardLimit, releaseLimit if releaseLimit > 0 else math.inf)
+ return limit
+
+
+def restoreCheckedStates(fit, stateInfo, ignoreModPoss=()):
+ if stateInfo is None:
+ return
+ changedMods, changedProjMods, changedProjDrones = stateInfo
+ for pos, state in changedMods.items():
+ if pos in ignoreModPoss:
+ continue
+ fit.modules[pos].state = state
+ for pos, state in changedProjMods.items():
+ fit.projectedModules[pos].state = state
+ for pos, amountActive in changedProjDrones.items():
+ fit.projectedDrones[pos].amountActive = amountActive
+
+
+def restoreRemovedDummies(fit, dummyInfo):
+ if dummyInfo is None:
+ return
+ # Need this to properly undo the case when removal of subsystems removes dummy slots
+ for position in sorted(dummyInfo):
+ slot = dummyInfo[position]
+ fit.modules.insert(position, Module.buildEmpty(slot))
+
+
+def getSimilarModPositions(mods, mainMod):
+ sMkt = Market.getInstance()
+ mainGroupID = getattr(sMkt.getGroupByItem(mainMod.item), 'ID', None)
+ mainMktGroupID = getattr(sMkt.getMarketGroupByItem(mainMod.item), 'ID', None)
+ mainEffects = set(getattr(mainMod.item, 'effects', ()))
+ positions = []
+ for position, mod in enumerate(mods):
+ if mod.isEmpty:
+ continue
+ # Always include selected module itself
+ if mod is mainMod:
+ positions.append(position)
+ continue
+ if mod.itemID is None:
+ continue
+ # Modules which have the same item ID
+ if mod.itemID == mainMod.itemID:
+ positions.append(position)
+ continue
+ # And modules from the same group and market group too
+ modGroupID = getattr(sMkt.getGroupByItem(mod.item), 'ID', None)
+ modMktGroupID = getattr(sMkt.getMarketGroupByItem(mod.item), 'ID', None)
+ modEffects = set(getattr(mod.item, 'effects', ()))
+ if (
+ modGroupID is not None and modGroupID == mainGroupID and
+ modMktGroupID is not None and modMktGroupID == mainMktGroupID and
+ modEffects == mainEffects
+ ):
+ positions.append(position)
+ continue
+ return positions
+
+
+def getSimilarFighters(fighters, mainFighter):
+ sMkt = Market.getInstance()
+ mainGroupID = getattr(sMkt.getGroupByItem(mainFighter.item), 'ID', None)
+ mainAbilityIDs = set(a.effectID for a in mainFighter.abilities)
+ similarFighters = []
+ for fighter in fighters:
+ # Always include selected fighter itself
+ if fighter is mainFighter:
+ similarFighters.append(fighter)
+ continue
+ if fighter.itemID is None:
+ continue
+ # Fighters which have the same item ID
+ if fighter.itemID == mainFighter.itemID:
+ similarFighters.append(fighter)
+ continue
+ # And fighters from the same group and with the same abilities too
+ fighterGroupID = getattr(sMkt.getGroupByItem(fighter.item), 'ID', None)
+ fighterAbilityIDs = set(a.effectID for a in fighter.abilities)
+ if (
+ fighterGroupID is not None and fighterGroupID == mainGroupID and
+ len(fighterAbilityIDs) > 0 and fighterAbilityIDs == mainAbilityIDs
+ ):
+ similarFighters.append(fighter)
+ continue
+ return similarFighters
+
+
+class CartInfo:
+
+ def __init__(self, itemID, amount):
+ self.itemID = itemID
+ self.amount = amount
+
+ @classmethod
+ def fromCart(cls, cart):
+ if cart is None:
+ return None
+ info = cls(
+ itemID=cart.itemID,
+ amount=cart.amount)
+ return info
+
+ def toCart(self):
+ item = Market.getInstance().getItem(self.itemID)
+ cart = Cart(item)
+ cart.amount = self.amount
+ return cart
+
+ def __repr__(self):
+ return makeReprStr(self, ['itemID', 'amount'])
+
+
+def activeStateLimit(itemIdentity):
+ item = Market.getInstance().getItem(itemIdentity)
+ if {
+ 'moduleBonusAssaultDamageControl', 'moduleBonusIndustrialInvulnerability',
+ 'microJumpDrive', 'microJumpPortalDrive', 'emergencyHullEnergizer',
+ 'cynosuralGeneration', 'jumpPortalGeneration', 'jumpPortalGenerationBO',
+ 'cloneJumpAccepting', 'cloakingWarpSafe', 'cloakingPrototype', 'cloaking',
+ 'massEntanglerEffect5', 'electronicAttributeModifyOnline', 'targetPassively',
+ 'cartScan', 'shipScan', 'surveyScan', 'targetSpectrumBreakerBonus',
+ 'interdictionNullifierBonus', 'warpCoreStabilizerActive',
+ 'industrialItemCompression'
+ }.intersection(item.effects):
+ return FittingModuleState.ONLINE
+ return FittingModuleState.ACTIVE
+
+
+def droneStackLimit(fit, itemIdentity):
+ item = Market.getInstance().getItem(itemIdentity)
+ hardLimit = max(5, fit.extraAttributes["maxActiveDrones"])
+ releaseLimit = fit.getReleaseLimitForDrone(item)
+ limit = min(hardLimit, releaseLimit if releaseLimit > 0 else math.inf)
+ return limit
+
+
+def restoreCheckedStates(fit, stateInfo, ignoreModPoss=()):
+ if stateInfo is None:
+ return
+ changedMods, changedProjMods, changedProjDrones = stateInfo
+ for pos, state in changedMods.items():
+ if pos in ignoreModPoss:
+ continue
+ fit.modules[pos].state = state
+ for pos, state in changedProjMods.items():
+ fit.projectedModules[pos].state = state
+ for pos, amountActive in changedProjDrones.items():
+ fit.projectedDrones[pos].amountActive = amountActive
+
+
+def restoreRemovedDummies(fit, dummyInfo):
+ if dummyInfo is None:
+ return
+ # Need this to properly undo the case when removal of subsystems removes dummy slots
+ for position in sorted(dummyInfo):
+ slot = dummyInfo[position]
+ fit.modules.insert(position, Module.buildEmpty(slot))
+
+
+def getSimilarModPositions(mods, mainMod):
+ sMkt = Market.getInstance()
+ mainGroupID = getattr(sMkt.getGroupByItem(mainMod.item), 'ID', None)
+ mainMktGroupID = getattr(sMkt.getMarketGroupByItem(mainMod.item), 'ID', None)
+ mainEffects = set(getattr(mainMod.item, 'effects', ()))
+ positions = []
+ for position, mod in enumerate(mods):
+ if mod.isEmpty:
+ continue
+ # Always include selected module itself
+ if mod is mainMod:
+ positions.append(position)
+ continue
+ if mod.itemID is None:
+ continue
+ # Modules which have the same item ID
+ if mod.itemID == mainMod.itemID:
+ positions.append(position)
+ continue
+ # And modules from the same group and market group too
+ modGroupID = getattr(sMkt.getGroupByItem(mod.item), 'ID', None)
+ modMktGroupID = getattr(sMkt.getMarketGroupByItem(mod.item), 'ID', None)
+ modEffects = set(getattr(mod.item, 'effects', ()))
+ if (
+ modGroupID is not None and modGroupID == mainGroupID and
+ modMktGroupID is not None and modMktGroupID == mainMktGroupID and
+ modEffects == mainEffects
+ ):
+ positions.append(position)
+ continue
+ return positions
+
+
+def getSimilarFighters(fighters, mainFighter):
+ sMkt = Market.getInstance()
+ mainGroupID = getattr(sMkt.getGroupByItem(mainFighter.item), 'ID', None)
+ mainAbilityIDs = set(a.effectID for a in mainFighter.abilities)
+ similarFighters = []
+ for fighter in fighters:
+ # Always include selected fighter itself
+ if fighter is mainFighter:
+ similarFighters.append(fighter)
+ continue
+ if fighter.itemID is None:
+ continue
+ # Fighters which have the same item ID
+ if fighter.itemID == mainFighter.itemID:
+ similarFighters.append(fighter)
+ continue
+ # And fighters from the same group and with the same abilities too
+ fighterGroupID = getattr(sMkt.getGroupByItem(fighter.item), 'ID', None)
+ fighterAbilityIDs = set(a.effectID for a in fighter.abilities)
+ if (
+ fighterGroupID is not None and fighterGroupID == mainGroupID and
+ len(fighterAbilityIDs) > 0 and fighterAbilityIDs == mainAbilityIDs
+ ):
+ similarFighters.append(fighter)
+ continue
+ return similarFighters
+
diff --git a/gui/fitCommands/gui/localModuleCart/__init__.py b/gui/fitCommands/gui/localModuleCart/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/gui/fitCommands/gui/localModuleCart/cartToLocalModule.py b/gui/fitCommands/gui/localModuleCart/cartToLocalModule.py
new file mode 100644
index 0000000000..5361ad728d
--- /dev/null
+++ b/gui/fitCommands/gui/localModuleCart/cartToLocalModule.py
@@ -0,0 +1,165 @@
+import wx
+
+import eos.db
+import gui.mainFrame
+from gui import globalEvents as GE
+from gui.fitCommands.calc.cart.add import CalcAddCartCommand
+from gui.fitCommands.calc.cart.remove import CalcRemoveCartCommand
+from gui.fitCommands.calc.module.changeCharges import CalcChangeModuleChargesCommand
+from gui.fitCommands.calc.module.localReplace import CalcReplaceLocalModuleCommand
+from gui.fitCommands.helpers import CartInfo, InternalCommandHistory, ModuleInfo, restoreRemovedDummies
+from service.fit import Fit
+
+
+class GuiCartToLocalModuleCommand(wx.Command):
+ """
+ Moves cart to the fitting window. If target is not empty, take whatever we take off and put
+ into the cart hold. If we copy, we do the same but do not remove the item from the cart hold.
+ """
+
+ def __init__(self, fitID, cartItemID, modPosition, copy):
+ wx.Command.__init__(self, True, 'Cart to Local Module')
+ self.internalHistory = InternalCommandHistory()
+ self.fitID = fitID
+ self.srcCartItemID = cartItemID
+ self.dstModPosition = modPosition
+ self.copy = copy
+ self.removedModItemID = None
+ self.addedModItemID = None
+ self.savedRemovedDummies = None
+
+ def Do(self):
+ sFit = Fit.getInstance()
+ fit = sFit.getFit(self.fitID)
+ srcCart = next((c for c in fit.cart if c.itemID == self.srcCartItemID), None)
+ if srcCart is None:
+ return False
+ dstMod = fit.modules[self.dstModPosition]
+ # Moving/copying charge from cart to fit
+ if srcCart.item.isCharge and not dstMod.isEmpty:
+ newCartChargeItemID = dstMod.chargeID
+ newCartChargeAmount = dstMod.numCharges
+ newModChargeItemID = self.srcCartItemID
+ newModChargeAmount = dstMod.getNumCharges(srcCart.item)
+ if newCartChargeItemID == newModChargeItemID:
+ return False
+ commands = []
+ if not self.copy:
+ commands.append(CalcRemoveCartCommand(
+ fitID=self.fitID,
+ cartInfo=CartInfo(itemID=newModChargeItemID, amount=newModChargeAmount)))
+ if newCartChargeItemID is not None:
+ commands.append(CalcAddCartCommand(
+ fitID=self.fitID,
+ cartInfo=CartInfo(itemID=newCartChargeItemID, amount=newCartChargeAmount)))
+ commands.append(CalcChangeModuleChargesCommand(
+ fitID=self.fitID,
+ projected=False,
+ chargeMap={self.dstModPosition: self.srcCartItemID}))
+ success = self.internalHistory.submitBatch(*commands)
+ # Moving/copying/replacing module
+ elif srcCart.item.isModule:
+ dstModItemID = dstMod.itemID
+ dstModSlot = dstMod.slot
+ if self.srcCartItemID == dstModItemID:
+ return False
+ # To keep all old item properties, copy them over from old module, except for mutations
+ newModInfo = ModuleInfo.fromModule(dstMod, unmutate=True)
+ newModInfo.itemID = self.srcCartItemID
+ if dstMod.isEmpty:
+ newCartModItemID = None
+ dstModChargeItemID = None
+ dstModChargeAmount = None
+ else:
+ # We cannot put mutated items to cart, so use unmutated item ID
+ newCartModItemID = ModuleInfo.fromModule(dstMod, unmutate=True).itemID
+ dstModChargeItemID = dstMod.chargeID
+ dstModChargeAmount = dstMod.numCharges
+ commands = []
+ # Keep cart only in case we were copying
+ if not self.copy:
+ commands.append(CalcRemoveCartCommand(
+ fitID=self.fitID,
+ cartInfo=CartInfo(itemID=self.srcCartItemID, amount=1)))
+ # Add item to cart only if we copied/moved to non-empty slot
+ if newCartModItemID is not None:
+ commands.append(CalcAddCartCommand(
+ fitID=self.fitID,
+ cartInfo=CartInfo(itemID=newCartModItemID, amount=1)))
+ cmdReplace = CalcReplaceLocalModuleCommand(
+ fitID=self.fitID,
+ position=self.dstModPosition,
+ newModInfo=newModInfo,
+ unloadInvalidCharges=True)
+ commands.append(cmdReplace)
+ # Submit batch now because we need to have updated info on fit to keep going
+ success = self.internalHistory.submitBatch(*commands)
+ newMod = fit.modules[self.dstModPosition]
+ # Bail if drag happened to slot to which module cannot be dragged, will undo later
+ if newMod.slot != dstModSlot:
+ success = False
+ if success:
+ # If we had to unload charge, add it to cart
+ if cmdReplace.unloadedCharge and dstModChargeItemID is not None:
+ cmdAddCartCharge = CalcAddCartCommand(
+ fitID=self.fitID,
+ cartInfo=CartInfo(itemID=dstModChargeItemID, amount=dstModChargeAmount))
+ success = self.internalHistory.submit(cmdAddCartCharge)
+ # If we did not unload charge and there still was a charge, see if amount differs and process it
+ elif not cmdReplace.unloadedCharge and dstModChargeItemID is not None:
+ # How many extra charges do we need to take from cart
+ extraChargeAmount = newMod.numCharges - dstModChargeAmount
+ if extraChargeAmount > 0:
+ cmdRemoveCartExtraCharge = CalcRemoveCartCommand(
+ fitID=self.fitID,
+ cartInfo=CartInfo(itemID=dstModChargeItemID, amount=extraChargeAmount))
+ # Do not check if operation was successful or not, we're okay if we have no such
+ # charges in cart
+ self.internalHistory.submit(cmdRemoveCartExtraCharge)
+ elif extraChargeAmount < 0:
+ cmdAddCartExtraCharge = CalcAddCartCommand(
+ fitID=self.fitID,
+ cartInfo=CartInfo(itemID=dstModChargeItemID, amount=abs(extraChargeAmount)))
+ success = self.internalHistory.submit(cmdAddCartExtraCharge)
+ if success:
+ # Store info to properly send events later
+ self.removedModItemID = dstModItemID
+ self.addedModItemID = self.srcCartItemID
+ else:
+ self.internalHistory.undoAll()
+ else:
+ return False
+ eos.db.flush()
+ sFit.recalc(self.fitID)
+ self.savedRemovedDummies = sFit.fill(self.fitID)
+ eos.db.commit()
+ events = []
+ if self.removedModItemID is not None:
+ events.append(GE.FitChanged(fitIDs=(self.fitID,), action='moddel', typeID=self.removedModItemID))
+ if self.addedModItemID is not None:
+ events.append(GE.FitChanged(fitIDs=(self.fitID,), action='modadd', typeID=self.addedModItemID))
+ if not events:
+ events.append(GE.FitChanged(fitIDs=(self.fitID,)))
+ for event in events:
+ wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), event)
+ return success
+
+ def Undo(self):
+ sFit = Fit.getInstance()
+ fit = sFit.getFit(self.fitID)
+ restoreRemovedDummies(fit, self.savedRemovedDummies)
+ success = self.internalHistory.undoAll()
+ eos.db.flush()
+ sFit.recalc(self.fitID)
+ sFit.fill(self.fitID)
+ eos.db.commit()
+ events = []
+ if self.addedModItemID is not None:
+ events.append(GE.FitChanged(fitIDs=(self.fitID,), action='moddel', typeID=self.addedModItemID))
+ if self.removedModItemID is not None:
+ events.append(GE.FitChanged(fitIDs=(self.fitID,), action='modadd', typeID=self.removedModItemID))
+ if not events:
+ events.append(GE.FitChanged(fitIDs=(self.fitID,)))
+ for event in events:
+ wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), event)
+ return success
diff --git a/gui/fitCommands/gui/localModuleCart/localModuleToCart.py b/gui/fitCommands/gui/localModuleCart/localModuleToCart.py
new file mode 100644
index 0000000000..964d96a5cb
--- /dev/null
+++ b/gui/fitCommands/gui/localModuleCart/localModuleToCart.py
@@ -0,0 +1,145 @@
+import wx
+
+import eos.db
+import gui.mainFrame
+from gui import globalEvents as GE
+from gui.fitCommands.calc.cart.add import CalcAddCartCommand
+from gui.fitCommands.calc.cart.remove import CalcRemoveCartCommand
+from gui.fitCommands.calc.module.localRemove import CalcRemoveLocalModulesCommand
+from gui.fitCommands.calc.module.localReplace import CalcReplaceLocalModuleCommand
+from gui.fitCommands.helpers import CartInfo, InternalCommandHistory, ModuleInfo, restoreRemovedDummies
+from service.fit import Fit
+
+
+class GuiLocalModuleToCartCommand(wx.Command):
+
+ def __init__(self, fitID, modPosition, cartItemID, copy):
+ wx.Command.__init__(self, True, 'Local Module to Cart')
+ self.internalHistory = InternalCommandHistory()
+ self.fitID = fitID
+ self.srcModPosition = modPosition
+ self.dstCartItemID = cartItemID
+ self.copy = copy
+ self.removedModItemID = None
+ self.addedModItemID = None
+ self.savedRemovedDummies = None
+
+ def Do(self):
+ fit = Fit.getInstance().getFit(self.fitID)
+ srcMod = fit.modules[self.srcModPosition]
+ if srcMod.isEmpty:
+ return False
+ srcModItemID = srcMod.itemID
+ dstCart = next((c for c in fit.cart if c.itemID == self.dstCartItemID), None)
+ success = False
+ # Attempt to swap if we're moving our module onto a module in the cart hold
+ if not self.copy and dstCart is not None and dstCart.item.isModule:
+ if srcModItemID == self.dstCartItemID:
+ return False
+ srcModSlot = srcMod.slot
+ newModInfo = ModuleInfo.fromModule(srcMod, unmutate=True)
+ newModInfo.itemID = self.dstCartItemID
+ srcModChargeItemID = srcMod.chargeID
+ srcModChargeAmount = srcMod.numCharges
+ commands = []
+ commands.append(CalcRemoveCartCommand(
+ fitID=self.fitID,
+ cartInfo=CartInfo(itemID=self.dstCartItemID, amount=1)))
+ commands.append(CalcAddCartCommand(
+ fitID=self.fitID,
+ # We cannot put mutated items to cart, so use unmutated item ID
+ cartInfo=CartInfo(itemID=ModuleInfo.fromModule(srcMod, unmutate=True).itemID, amount=1)))
+ cmdReplace = CalcReplaceLocalModuleCommand(
+ fitID=self.fitID,
+ position=self.srcModPosition,
+ newModInfo=newModInfo,
+ unloadInvalidCharges=True)
+ commands.append(cmdReplace)
+ # Submit batch now because we need to have updated info on fit to keep going
+ success = self.internalHistory.submitBatch(*commands)
+ if success:
+ newMod = fit.modules[self.srcModPosition]
+ # Process charge changes if module is moved to proper slot
+ if newMod.slot == srcModSlot:
+ # If we had to unload charge, add it to cart
+ if cmdReplace.unloadedCharge and srcModChargeItemID is not None:
+ cmdAddCartCharge = CalcAddCartCommand(
+ fitID=self.fitID,
+ cartInfo=CartInfo(itemID=srcModChargeItemID, amount=srcModChargeAmount))
+ success = self.internalHistory.submit(cmdAddCartCharge)
+ # If we did not unload charge and there still was a charge, see if amount differs and process it
+ elif not cmdReplace.unloadedCharge and srcModChargeItemID is not None:
+ # How many extra charges do we need to take from cart
+ extraChargeAmount = newMod.numCharges - srcModChargeAmount
+ if extraChargeAmount > 0:
+ cmdRemoveCartExtraCharge = CalcRemoveCartCommand(
+ fitID=self.fitID,
+ cartInfo=CartInfo(itemID=srcModChargeItemID, amount=extraChargeAmount))
+ # Do not check if operation was successful or not, we're okay if we have no such
+ # charges in cart
+ self.internalHistory.submit(cmdRemoveCartExtraCharge)
+ elif extraChargeAmount < 0:
+ cmdAddCartExtraCharge = CalcAddCartCommand(
+ fitID=self.fitID,
+ cartInfo=CartInfo(itemID=srcModChargeItemID, amount=abs(extraChargeAmount)))
+ success = self.internalHistory.submit(cmdAddCartExtraCharge)
+ if success:
+ # Store info to properly send events later
+ self.removedModItemID = srcModItemID
+ self.addedModItemID = self.dstCartItemID
+ # If drag happened to module which cannot be fit into current slot - consider it as failure
+ else:
+ success = False
+ # And in case of any failures, cancel everything to try to do move instead
+ if not success:
+ self.internalHistory.undoAll()
+ # Just dump module and its charges into cart when copying or moving to cart
+ if not success:
+ commands = []
+ commands.append(CalcAddCartCommand(
+ fitID=self.fitID,
+ cartInfo=CartInfo(itemID=ModuleInfo.fromModule(srcMod, unmutate=True).itemID, amount=1)))
+ if srcMod.chargeID is not None:
+ commands.append(CalcAddCartCommand(
+ fitID=self.fitID,
+ cartInfo=CartInfo(itemID=srcMod.chargeID, amount=srcMod.numCharges)))
+ if not self.copy:
+ commands.append(CalcRemoveLocalModulesCommand(
+ fitID=self.fitID,
+ positions=[self.srcModPosition]))
+ success = self.internalHistory.submitBatch(*commands)
+ eos.db.flush()
+ sFit = Fit.getInstance()
+ sFit.recalc(self.fitID)
+ self.savedRemovedDummies = sFit.fill(self.fitID)
+ eos.db.commit()
+ events = []
+ if self.removedModItemID is not None:
+ events.append(GE.FitChanged(fitIDs=(self.fitID,), action='moddel', typeID=self.removedModItemID))
+ if self.addedModItemID is not None:
+ events.append(GE.FitChanged(fitIDs=(self.fitID,), action='modadd', typeID=self.addedModItemID))
+ if not events:
+ events.append(GE.FitChanged(fitIDs=(self.fitID,)))
+ for event in events:
+ wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), event)
+ return success
+
+ def Undo(self):
+ sFit = Fit.getInstance()
+ fit = sFit.getFit(self.fitID)
+ restoreRemovedDummies(fit, self.savedRemovedDummies)
+ success = self.internalHistory.undoAll()
+ eos.db.flush()
+ sFit.recalc(self.fitID)
+ sFit.fill(self.fitID)
+ eos.db.commit()
+ events = []
+ if self.addedModItemID is not None:
+ events.append(GE.FitChanged(fitIDs=(self.fitID,), action='moddel', typeID=self.addedModItemID))
+ if self.removedModItemID is not None:
+ events.append(GE.FitChanged(fitIDs=(self.fitID,), action='modadd', typeID=self.removedModItemID))
+ if not events:
+ events.append(GE.FitChanged(fitIDs=(self.fitID,)))
+ for event in events:
+ wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), event)
+ return success
diff --git a/gui/fitCommands/helpers.py b/gui/fitCommands/helpers.py
index 58a48b9355..3616c6618e 100644
--- a/gui/fitCommands/helpers.py
+++ b/gui/fitCommands/helpers.py
@@ -7,6 +7,7 @@
from eos.const import FittingModuleState
from eos.saveddata.booster import Booster
from eos.saveddata.cargo import Cargo
+from eos.saveddata.cart import Cart
from eos.saveddata.drone import Drone
from eos.saveddata.fighter import Fighter
from eos.saveddata.implant import Implant
@@ -345,6 +346,30 @@ def toCargo(self):
def __repr__(self):
return makeReprStr(self, ['itemID', 'amount'])
+class CartInfo:
+
+ def __init__(self, itemID, amount):
+ self.itemID = itemID
+ self.amount = amount
+
+ @classmethod
+ def fromCart(cls, cart):
+ if cart is None:
+ return None
+ info = cls(
+ itemID=cart.itemID,
+ amount=cart.amount)
+ return info
+
+ def toCart(self):
+ item = Market.getInstance().getItem(self.itemID)
+ cart = Cart(item)
+ cart.amount = self.amount
+ return cart
+
+ def __repr__(self):
+ return makeReprStr(self, ['itemID', 'amount'])
+
def activeStateLimit(itemIdentity):
item = Market.getInstance().getItem(itemIdentity)
diff --git a/imgs/gui/cart_small.png b/imgs/gui/cart_small.png
new file mode 100644
index 0000000000..8f7b165ae0
Binary files /dev/null and b/imgs/gui/cart_small.png differ
diff --git a/pyfa.py b/pyfa.py
index 706af752c3..ae8d57ab0a 100755
--- a/pyfa.py
+++ b/pyfa.py
@@ -67,13 +67,20 @@ def _process_args(self, largs, rargs, values):
# Parse command line options
usage = "usage: %prog [--root]"
parser = PassThroughOptionParser(usage=usage)
-parser.add_option("-r", "--root", action="store_true", dest="rootsavedata", help="if you want pyfa to store its data in root folder, use this option", default=False)
+parser.add_option("-r", "--root", action="store_true", dest="rootsavedata", help="if you want pyfa to store its data "
+ "in root folder, use this option",
+ default=False)
parser.add_option("-d", "--debug", action="store_true", dest="debug", help="Set logger to debug level.", default=False)
parser.add_option("-t", "--title", action="store", dest="title", help="Set Window Title", default=None)
parser.add_option("-s", "--savepath", action="store", dest="savepath", help="Set the folder for savedata", default=None)
-parser.add_option("-l", "--logginglevel", action="store", dest="logginglevel", help="Set desired logging level [Critical|Error|Warning|Info|Debug]", default="Error")
-parser.add_option("-p", "--profile", action="store", dest="profile_path", help="Set location to save profileing.", default=None)
-parser.add_option("-i", "--language", action="store", dest="language", help="Sets the language for pyfa. Overrides user's saved settings. Format: xx_YY (eg: en_US). If translation doesn't exist, defaults to en_US", default=None)
+parser.add_option("-l", "--logginglevel", action="store", dest="logginglevel",
+ help="Set desired logging level [""Critical|Error|Warning|Info|Debug]", default="Error")
+parser.add_option("-p", "--profile", action="store", dest="profile_path", help="Set location to save profileing.",
+ default=None)
+parser.add_option("-i", "--language", action="store", dest="language", help="Sets the language for pyfa. Overrides "
+ "user's saved settings. Format: xx_YY ("
+ "eg: en_US). If translation doesn't "
+ "exist, defaults to en_US", default=None)
(options, args) = parser.parse_args()
@@ -151,7 +158,8 @@ def _process_args(self, largs, rargs, values):
ErrorHandler.SetParent(mf)
if options.profile_path:
- profile_path = os.path.join(options.profile_path, 'pyfa-{}.profile'.format(datetime.datetime.now().strftime('%Y%m%d_%H%M%S')))
+ profile_path = os.path.join(options.profile_path,
+ 'pyfa-{}.profile'.format(datetime.datetime.now().strftime('%Y%m%d_%H%M%S')))
pyfalog.debug("Starting pyfa with a profiler, saving to {}".format(profile_path))
import cProfile
diff --git a/requirements.txt b/requirements.txt
index 4a95eb125c..d28327b6a9 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,6 @@
-wxPython == 4.0.6
+wxPython >= 4.0.6
logbook >= 1.0.0
-numpy == 1.19.2
+numpy >= 1.19.2
matplotlib == 3.2.2
python-dateutil
requests >= 2.0.0