Merge remote-tracking branch 'origin/projections'

This commit is contained in:
blitzmann
2015-07-23 15:46:32 -04:00
18 changed files with 828 additions and 329 deletions

View File

@@ -17,7 +17,7 @@ debug = False
# Defines if our saveddata will be in pyfa root or not
saveInRoot = False
logLevel = logging.WARN
logLevel = logging.DEBUG
# Version data
version = "1.13.3"
@@ -32,6 +32,21 @@ staticPath = None
saveDB = None
gameDB = None
class StreamToLogger(object):
"""
Fake file-like stream object that redirects writes to a logger instance.
From: http://www.electricmonk.nl/log/2011/08/14/redirect-stdout-and-stderr-to-a-logger-in-python/
"""
def __init__(self, logger, log_level=logging.INFO):
self.logger = logger
self.log_level = log_level
self.linebuf = ''
def write(self, buf):
for line in buf.rstrip().splitlines():
self.logger.log(self.log_level, line.rstrip())
def __createDirs(path):
if not os.path.exists(path):
os.makedirs(path)
@@ -72,15 +87,14 @@ def defPaths():
logging.info("Starting pyfa")
# Redirect stderr to file if we're requested to do so
stderrToFile = getattr(configforced, "stderrToFile", None)
if stderrToFile is True:
sys.stderr = open(os.path.join(savePath, "error_log.txt"), "w")
if hasattr(sys, 'frozen'):
stdout_logger = logging.getLogger('STDOUT')
sl = StreamToLogger(stdout_logger, logging.INFO)
sys.stdout = sl
# Same for stdout
stdoutToFile = getattr(configforced, "stdoutToFile", None)
if stdoutToFile is True:
sys.stdout = open(os.path.join(savePath, "output_log.txt"), "w")
stderr_logger = logging.getLogger('STDERR')
sl = StreamToLogger(stderr_logger, logging.ERROR)
sys.stderr = sl
# Static EVE Data from the staticdata repository, should be in the staticdata
# directory in our pyfa directory

View File

@@ -0,0 +1,16 @@
"""
Migration 10
- Adds active attribute to projected fits
"""
import sqlalchemy
def upgrade(saveddata_engine):
# Update projectedFits schema to include active attribute
try:
saveddata_engine.execute("SELECT active FROM projectedFits LIMIT 1")
except sqlalchemy.exc.DatabaseError:
saveddata_engine.execute("ALTER TABLE projectedFits ADD COLUMN active BOOLEAN")
saveddata_engine.execute("UPDATE projectedFits SET active = 1")
saveddata_engine.execute("UPDATE projectedFits SET amount = 1")

View File

@@ -17,9 +17,11 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>.
#===============================================================================
from sqlalchemy import Table, Column, Integer, ForeignKey, String, Boolean
from sqlalchemy.orm import relation, mapper
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.sql import and_
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.orm.collections import attribute_mapped_collection
from eos.db import saveddata_meta
from eos.db.saveddata.module import modules_table
@@ -45,33 +47,119 @@ fits_table = Table("fits", saveddata_meta,
projectedFits_table = Table("projectedFits", saveddata_meta,
Column("sourceID", ForeignKey("fits.ID"), primary_key = True),
Column("victimID", ForeignKey("fits.ID"), primary_key = True),
Column("amount", Integer))
Column("amount", Integer, nullable = False, default = 1),
Column("active", Boolean, nullable = False, default = 1),
)
class ProjectedFit(object):
def __init__(self, sourceID, source_fit, amount=1, active=True):
self.sourceID = sourceID
self.source_fit = source_fit
self.active = active
self.__amount = amount
@reconstructor
def init(self):
if self.source_fit.isInvalid:
# Very rare for this to happen, but be prepared for it
eos.db.saveddata_session.delete(self.source_fit)
eos.db.saveddata_session.flush()
eos.db.saveddata_session.refresh(self.victim_fit)
# We have a series of setters and getters here just in case someone
# downgrades and screws up the table with NULL values
@property
def amount(self):
return self.__amount or 1
@amount.setter
def amount(self, amount):
self.__amount = amount
def __repr__(self):
return "ProjectedFit(sourceID={}, victimID={}, amount={}, active={}) at {}".format(
self.sourceID, self.victimID, self.amount, self.active, hex(id(self))
)
Fit._Fit__projectedFits = association_proxy(
"victimOf", # look at the victimOf association...
"source_fit", # .. and return the source fits
creator=lambda sourceID, source_fit: ProjectedFit(sourceID, source_fit)
)
mapper(Fit, fits_table,
properties = {"_Fit__modules" : relation(Module, collection_class = HandledModuleList,
primaryjoin = and_(modules_table.c.fitID == fits_table.c.ID, modules_table.c.projected == False),
order_by = modules_table.c.position, cascade='all, delete, delete-orphan'),
"_Fit__projectedModules" : relation(Module, collection_class = HandledProjectedModList, cascade='all, delete, delete-orphan', single_parent=True,
primaryjoin = and_(modules_table.c.fitID == fits_table.c.ID, modules_table.c.projected == True)),
"owner" : relation(User, backref = "fits"),
"itemID" : fits_table.c.shipID,
"shipID" : fits_table.c.shipID,
"_Fit__boosters" : relation(Booster, collection_class = HandledImplantBoosterList, cascade='all, delete, delete-orphan', single_parent=True),
"_Fit__drones" : relation(Drone, collection_class = HandledDroneCargoList, 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 = HandledDroneCargoList, 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', backref='fit', single_parent=True,
primaryjoin = fitImplants_table.c.fitID == fits_table.c.ID,
secondaryjoin = fitImplants_table.c.implantID == Implant.ID,
secondary = fitImplants_table),
"_Fit__character" : relation(Character, backref = "fits"),
"_Fit__damagePattern" : relation(DamagePattern),
"_Fit__targetResists" : relation(TargetResists),
"_Fit__projectedFits" : relation(Fit,
primaryjoin = projectedFits_table.c.victimID == fits_table.c.ID,
secondaryjoin = fits_table.c.ID == projectedFits_table.c.sourceID,
secondary = projectedFits_table,
collection_class = HandledProjectedFitList)
})
properties = {
"_Fit__modules": relation(
Module,
collection_class=HandledModuleList,
primaryjoin=and_(modules_table.c.fitID == fits_table.c.ID, modules_table.c.projected == False),
order_by=modules_table.c.position,
cascade='all, delete, delete-orphan'),
"_Fit__projectedModules": relation(
Module,
collection_class=HandledProjectedModList,
cascade='all, delete, delete-orphan',
single_parent=True,
primaryjoin=and_(modules_table.c.fitID == fits_table.c.ID, modules_table.c.projected == True)),
"owner": relation(
User,
backref="fits"),
"itemID": fits_table.c.shipID,
"shipID": fits_table.c.shipID,
"_Fit__boosters": relation(
Booster,
collection_class=HandledImplantBoosterList,
cascade='all, delete, delete-orphan',
single_parent=True),
"_Fit__drones": relation(
Drone,
collection_class=HandledDroneCargoList,
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=HandledDroneCargoList,
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',
backref='fit',
single_parent=True,
primaryjoin=fitImplants_table.c.fitID == fits_table.c.ID,
secondaryjoin=fitImplants_table.c.implantID == Implant.ID,
secondary=fitImplants_table),
"_Fit__character": relation(
Character,
backref="fits"),
"_Fit__damagePattern": relation(DamagePattern),
"_Fit__targetResists": relation(TargetResists),
"projectedOnto": relationship(
ProjectedFit,
primaryjoin=projectedFits_table.c.sourceID == fits_table.c.ID,
backref='source_fit',
collection_class=attribute_mapped_collection('victimID'),
cascade='all, delete, delete-orphan'),
"victimOf": relationship(
ProjectedFit,
primaryjoin=fits_table.c.ID == projectedFits_table.c.victimID,
backref='victim_fit',
collection_class=attribute_mapped_collection('sourceID'),
cascade='all, delete, delete-orphan'),
}
)
mapper(ProjectedFit, projectedFits_table,
properties = {
"_ProjectedFit__amount": projectedFits_table.c.amount,
}
)

View File

@@ -232,11 +232,6 @@ class HandledProjectedDroneList(HandledDroneCargoList):
if proj.isInvalid or not proj.item.isType("projected"):
self.remove(proj)
class HandledProjectedFitList(HandledList):
def append(self, proj):
proj.projected = True
list.append(self, proj)
class HandledItem(object):
def preAssignItemAttr(self, *args, **kwargs):
self.itemModifiedAttributes.preAssign(*args, **kwargs)

View File

@@ -217,13 +217,16 @@ class ModifiedAttributeDict(collections.MutableMapping):
if attributeName not in self.__affectedBy:
self.__affectedBy[attributeName] = {}
affs = self.__affectedBy[attributeName]
origin = self.fit.getOrigin()
fit = origin if origin and origin != self.fit else self.fit
# If there's no set for current fit in dictionary, create it
if self.fit not in affs:
affs[self.fit] = []
if fit not in affs:
affs[fit] = []
# Reassign alias to list
affs = affs[self.fit]
affs = affs[fit]
# Get modifier which helps to compose 'Affected by' map
modifier = self.fit.getModifier()
# Add current affliction to list
affs.append((modifier, operation, bonus, used))

View File

@@ -286,5 +286,10 @@ class Skill(HandledItem):
copy = Skill(self.item, self.level, self.__ro)
return copy
def __repr__(self):
return "Skill(ID={}, name={}) at {}".format(
self.item.ID, self.item.name, hex(id(self))
)
class ReadOnlyException(Exception):
pass

View File

@@ -29,6 +29,9 @@ from eos.saveddata.module import State
from eos.saveddata.mode import Mode
import eos.db
import time
import copy
from utils.timer import Timer
import logging
logger = logging.getLogger(__name__)
@@ -40,14 +43,6 @@ except ImportError:
class Fit(object):
"""Represents a fitting, with modules, ship, implants, etc."""
EXTRA_ATTRIBUTES = {"armorRepair": 0,
"hullRepair": 0,
"shieldRepair": 0,
"maxActiveDrones": 0,
"maxTargetsLockedFromSkills": 2,
"droneControlRange": 20000,
"cloaked": False,
"siege": False}
PEAK_RECHARGE = 0.25
@@ -61,7 +56,7 @@ class Fit(object):
self.__cargo = HandledDroneCargoList()
self.__implants = HandledImplantBoosterList()
self.__boosters = HandledImplantBoosterList()
self.__projectedFits = HandledProjectedFitList()
#self.__projectedFits = {}
self.__projectedModules = HandledProjectedModList()
self.__projectedDrones = HandledProjectedDroneList()
self.__character = None
@@ -88,6 +83,10 @@ class Fit(object):
try:
self.__ship = Ship(item)
# @todo extra attributes is now useless, however it set to be
# the same as ship attributes for ease (so we don't have to
# change all instances in source). Remove this at some point
self.extraAttributes = self.__ship.itemModifiedAttributes
except ValueError:
logger.error("Item (id: %d) is not a Ship", self.shipID)
return
@@ -124,8 +123,6 @@ class Fit(object):
self.boostsFits = set()
self.gangBoosts = None
self.ecmProjectedStr = 1
self.extraAttributes = ModifiedAttributeDict(self)
self.extraAttributes.original = self.EXTRA_ATTRIBUTES
@property
def targetResists(self):
@@ -178,8 +175,11 @@ class Fit(object):
def ship(self, ship):
self.__ship = ship
self.shipID = ship.item.ID if ship is not None else None
# set mode of new ship
self.mode = self.ship.validateModeItem(None) if ship is not None else None
if ship is not None:
# set mode of new ship
self.mode = self.ship.validateModeItem(None) if ship is not None else None
# set fit attributes the same as ship
self.extraAttributes = self.ship.itemModifiedAttributes
@property
def drones(self):
@@ -207,7 +207,12 @@ class Fit(object):
@property
def projectedFits(self):
return self.__projectedFits
# only in extreme edge cases will the fit be invalid, but to be sure do
# not return them.
return [fit for fit in self.__projectedFits.values() if not fit.isInvalid]
def getProjectionInfo(self, fitID):
return self.projectedOnto.get(fitID, None)
@property
def projectedDrones(self):
@@ -326,7 +331,7 @@ class Fit(object):
if map[key](val) == False: raise ValueError(str(val) + " is not a valid value for " + key)
else: return val
def clear(self):
def clear(self, projected=False):
self.__effectiveTank = None
self.__weaponDPS = None
self.__minerYield = None
@@ -346,15 +351,36 @@ class Fit(object):
del self.__calculatedTargets[:]
del self.__extraDrains[:]
if self.ship is not None: self.ship.clear()
c = chain(self.modules, self.drones, self.boosters, self.implants, self.projectedDrones, self.projectedModules, self.projectedFits, (self.character, self.extraAttributes))
if self.ship:
self.ship.clear()
c = chain(
self.modules,
self.drones,
self.boosters,
self.implants,
self.projectedDrones,
self.projectedModules,
(self.character, self.extraAttributes),
)
for stuff in c:
if stuff is not None and stuff != self: stuff.clear()
if stuff is not None and stuff != self:
stuff.clear()
# If this is the active fit that we are clearing, not a projected fit,
# then this will run and clear the projected ships and flag the next
# iteration to skip this part to prevent recursion.
if not projected:
for stuff in self.projectedFits:
if stuff is not None and stuff != self:
stuff.clear(projected=True)
#Methods to register and get the thing currently affecting the fit,
#so we can correctly map "Affected By"
def register(self, currModifier):
def register(self, currModifier, origin=None):
self.__modifier = currModifier
self.__origin = origin
if hasattr(currModifier, "itemModifiedAttributes"):
currModifier.itemModifiedAttributes.fit = self
if hasattr(currModifier, "chargeModifiedAttributes"):
@@ -363,98 +389,129 @@ class Fit(object):
def getModifier(self):
return self.__modifier
def getOrigin(self):
return self.__origin
def __calculateGangBoosts(self, runTime):
logger.debug("Applying gang boosts in `%s` runtime for %s", runTime, repr(self))
for name, info in self.gangBoosts.iteritems():
# Unpack all data required to run effect properly
effect, thing = info[1]
if effect.runTime == runTime:
context = ("gang", thing.__class__.__name__.lower())
if isinstance(thing, Module):
if effect.isType("offline") or (effect.isType("passive") and thing.state >= State.ONLINE) or \
(effect.isType("active") and thing.state >= State.ACTIVE):
# Run effect, and get proper bonuses applied
try:
self.register(thing)
effect.handler(self, thing, context)
except:
pass
else:
# Run effect, and get proper bonuses applied
try:
self.register(thing)
effect.handler(self, thing, context)
except:
pass
def calculateModifiedAttributes(self, targetFit=None, withBoosters=False, dirtyStorage=None):
refreshBoosts = False
if withBoosters is True:
refreshBoosts = True
if dirtyStorage is not None and self.ID in dirtyStorage:
refreshBoosts = True
if dirtyStorage is not None:
dirtyStorage.update(self.boostsFits)
if self.fleet is not None and refreshBoosts is True:
self.gangBoosts = self.fleet.recalculateLinear(withBoosters=withBoosters, dirtyStorage=dirtyStorage)
timer = Timer('Fit: {}, {}'.format(self.ID, self.name), logger)
logger.debug("Starting fit calculation on: %s, withBoosters: %s", repr(self), withBoosters)
shadow = False
if targetFit:
logger.debug("Applying projections to target: %s", repr(targetFit))
projectionInfo = self.getProjectionInfo(targetFit.ID)
logger.debug("ProjectionInfo: %s", projectionInfo)
if self == targetFit:
copied = self # original fit
shadow = True
self = copy.deepcopy(self)
self.fleet = copied.fleet
logger.debug("Handling self projection - making shadow copy of fit. %s => %s", repr(copied), repr(self))
# we delete the fit because when we copy a fit, flush() is
# called to properly handle projection updates. However, we do
# not want to save this fit to the database, so simply remove it
eos.db.saveddata_session.delete(self)
if self.fleet is not None and withBoosters is True:
logger.debug("Fleet is set, gathering gang boosts")
self.gangBoosts = self.fleet.recalculateLinear(withBoosters=withBoosters)
timer.checkpoint("Done calculating gang boosts for %s"%repr(self))
elif self.fleet is None:
self.gangBoosts = None
if dirtyStorage is not None:
try:
dirtyStorage.remove(self.ID)
except KeyError:
pass
# If we're not explicitly asked to project fit onto something,
# set self as target fit
if targetFit is None:
targetFit = self
forceProjected = False
# Else, we're checking all target projectee fits
elif targetFit not in self.__calculatedTargets:
self.__calculatedTargets.append(targetFit)
targetFit.calculateModifiedAttributes(dirtyStorage=dirtyStorage)
forceProjected = True
# Or do nothing if target fit is calculated
projected = False
else:
return
projected = True
# If fit is calculated and we have nothing to do here, get out
if self.__calculated == True and forceProjected == False:
# A note on why projected fits don't get to return here. If we return
# here, the projection afflictions will not be run as they are
# intertwined into the regular fit calculations. So, even if the fit has
# been calculated, we need to recalculate it again just to apply the
# projections. This is in contract to gang boosts, which are only
# calculated once, and their items are then looped and accessed with
# self.gangBoosts.iteritems()
# We might be able to exit early in the fit calculations if we separate
# projections from the normal fit calculations. But we must ensure that
# projection have modifying stuff applied, such as gang boosts and other
# local modules that may help
if self.__calculated and not projected:
logger.debug("Fit has already been calculated and is not projected, returning: %s", repr(self))
return
# Mark fit as calculated
self.__calculated = True
# There's a few things to keep in mind here
# 1: Early effects first, then regular ones, then late ones, regardless of anything else
# 2: Some effects aren't implemented
# 3: Some effects are implemented poorly and will just explode on us
# 4: Errors should be handled gracefully and preferably without crashing unless serious
for runTime in ("early", "normal", "late"):
# Build a little chain of stuff
# Avoid adding projected drones and modules when fit is projected onto self
# TODO: remove this workaround when proper self-projection using virtual duplicate fits is implemented
if forceProjected is True:
# if fit is being projected onto another fit
c = chain((self.character, self.ship), self.drones, self.boosters, self.appliedImplants, self.modules)
else:
c = chain((self.character, self.ship, self.mode), self.drones, self.boosters, self.appliedImplants, self.modules,
self.projectedDrones, self.projectedModules)
c = chain(
(self.character, self.ship),
self.drones,
self.boosters,
self.appliedImplants,
self.modules
)
if not projected:
# if not a projected fit, add a couple of more things
c = chain(c, (self.mode,), self.projectedDrones, self.projectedModules)
# We calculate gang bonuses first so that projected fits get them
if self.gangBoosts is not None:
contextMap = {Skill: "skill",
Ship: "ship",
Module: "module",
Implant: "implant"}
for name, info in self.gangBoosts.iteritems():
# Unpack all data required to run effect properly
effect, thing = info[1]
if effect.runTime == runTime:
context = ("gang", contextMap[type(thing)])
if isinstance(thing, Module):
if effect.isType("offline") or (effect.isType("passive") and thing.state >= State.ONLINE) or \
(effect.isType("active") and thing.state >= State.ACTIVE):
# Run effect, and get proper bonuses applied
try:
self.register(thing)
effect.handler(self, thing, context)
except:
pass
else:
# Run effect, and get proper bonuses applied
try:
self.register(thing)
effect.handler(self, thing, context)
except:
pass
self.__calculateGangBoosts(runTime)
for item in c:
# Registering the item about to affect the fit allows us to track "Affected By" relations correctly
# Registering the item about to affect the fit allows us to
# track "Affected By" relations correctly
if item is not None:
self.register(item)
item.calculateModifiedAttributes(self, runTime, False)
if forceProjected is True:
targetFit.register(item)
item.calculateModifiedAttributes(targetFit, runTime, True)
if projected is True:
for _ in xrange(projectionInfo.amount):
targetFit.register(item, origin=self)
item.calculateModifiedAttributes(targetFit, runTime, True)
for fit in self.projectedFits:
fit.calculateModifiedAttributes(self, withBoosters=withBoosters, dirtyStorage=dirtyStorage)
timer.checkpoint('Done with runtime: %s'%runTime)
# Only apply projected fits if fit it not projected itself.
if not projected:
for fit in self.projectedFits:
if fit.getProjectionInfo(self.ID).active:
fit.calculateModifiedAttributes(self, withBoosters=withBoosters, dirtyStorage=dirtyStorage)
timer.checkpoint('Done with fit calculation')
if shadow:
logger.debug("Delete shadow fit object")
del self
def fill(self):
"""
@@ -927,6 +984,19 @@ class Fit(object):
c.append(deepcopy(i, memo))
for fit in self.projectedFits:
copy.projectedFits.append(fit)
copy.__projectedFits[fit.ID] = fit
# this bit is required -- see GH issue # 83
eos.db.saveddata_session.flush()
eos.db.saveddata_session.refresh(fit)
return copy
def __repr__(self):
return "Fit(ID={}, ship={}, name={}) at {}".format(
self.ID, self.ship.item.name, self.name, hex(id(self))
)
def __str__(self):
return "{} ({})".format(
self.name, self.ship.item.name
)

View File

@@ -639,6 +639,14 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
copy.state = self.state
return copy
def __repr__(self):
if self.item:
return "Module(ID={}, name={}) at {}".format(
self.item.ID, self.item.name, hex(id(self))
)
else:
return "EmptyModule() at {}".format(hex(id(self)))
class Rack(Module):
'''
This is simply the Module class named something else to differentiate

View File

@@ -26,6 +26,17 @@ import logging
logger = logging.getLogger(__name__)
class Ship(ItemAttrShortcut, HandledItem):
EXTRA_ATTRIBUTES = {
"armorRepair": 0,
"hullRepair": 0,
"shieldRepair": 0,
"maxActiveDrones": 0,
"maxTargetsLockedFromSkills": 2,
"droneControlRange": 20000,
"cloaked": False,
"siege": False
}
def __init__(self, item):
if item.category.name != "Ship":
@@ -34,7 +45,8 @@ class Ship(ItemAttrShortcut, HandledItem):
self.__item = item
self.__modeItems = self.__getModeItems()
self.__itemModifiedAttributes = ModifiedAttributeDict()
self.__itemModifiedAttributes.original = self.item.attributes
self.__itemModifiedAttributes.original = dict(self.item.attributes)
self.__itemModifiedAttributes.original.update(self.EXTRA_ATTRIBUTES)
self.commandBonus = 0

View File

@@ -17,5 +17,6 @@ __all__ = [
#"changeAffectingSkills",
"tacticalMode",
"targetResists",
"priceClear"
"priceClear",
"amount",
]

View File

@@ -0,0 +1,76 @@
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 ChangeAmount(ContextMenu):
def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
def display(self, srcContext, selection):
return srcContext in ("cargoItem","projectedFit")
def getText(self, itmContext, selection):
print selection
return "Change {0} Quantity".format(itmContext)
def activate(self, fullContext, selection, i):
srcContext = fullContext[0]
dlg = AmountChanger(self.mainFrame, selection[0], srcContext)
dlg.ShowModal()
dlg.Destroy()
ChangeAmount.register()
class AmountChanger(wx.Dialog):
def __init__(self, parent, thing, context):
wx.Dialog.__init__(self, parent, title="Select Amount", size=wx.Size(220, 60))
self.thing = thing
self.context = context
bSizer1 = wx.BoxSizer(wx.HORIZONTAL)
self.input = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_PROCESS_ENTER)
bSizer1.Add(self.input, 1, wx.ALL, 5)
self.input.Bind(wx.EVT_CHAR, self.onChar)
self.input.Bind(wx.EVT_TEXT_ENTER, self.change)
self.button = wx.Button(self, wx.ID_OK, u"Done")
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()
if isinstance(self.thing, eos.types.Cargo):
sFit.addCargo(fitID, self.thing.item.ID, int(self.input.GetLineText(0)), replace=True)
elif isinstance(self.thing, eos.types.Fit):
sFit.changeAmount(fitID, self.thing, int(self.input.GetLineText(0)))
wx.PostEvent(mainFrame, GE.FitChanged(fitID=fitID))
event.Skip()
self.Destroy()
## 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

View File

@@ -29,67 +29,3 @@ class Cargo(ContextMenu):
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, style=wx.TE_PROCESS_ENTER)
bSizer1.Add(self.input, 1, wx.ALL, 5)
self.input.Bind(wx.EVT_CHAR, self.onChar)
self.input.Bind(wx.EVT_TEXT_ENTER, self.change)
self.button = wx.Button(self, wx.ID_OK, u"Done")
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.addCargo(fitID, self.cargo.item.ID, int(self.input.GetLineText(0)), replace=True)
wx.PostEvent(mainFrame, GE.FitChanged(fitID=fitID))
event.Skip()
self.Destroy()
## 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

View File

@@ -15,7 +15,8 @@ class ItemStats(ContextMenu):
"cargoItem", "droneItem",
"implantItem", "boosterItem",
"skillItem", "projectedModule",
"projectedDrone", "projectedCharge")
"projectedDrone", "projectedCharge",
"itemStats")
def getText(self, itmContext, selection):
return "{0} Stats".format(itmContext if itmContext is not None else "Item")

View File

@@ -18,9 +18,9 @@
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
#===============================================================================
from gui import builtinViewColumns
from gui.viewColumn import ViewColumn
from gui import bitmapLoader
import gui.mainFrame
import wx
from eos.types import Drone, Cargo, Fit, Module, Slot, Rack
import service
@@ -29,9 +29,12 @@ class BaseName(ViewColumn):
name = "Base Name"
def __init__(self, fittingView, params):
ViewColumn.__init__(self, fittingView)
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.columnText = "Name"
self.shipImage = fittingView.imageList.GetImageIndex("ship_small", "icons")
self.mask = wx.LIST_MASK_TEXT
self.projectedView = isinstance(fittingView, gui.projectedView.ProjectedView)
def getText(self, stuff):
if isinstance(stuff, Drone):
@@ -39,7 +42,12 @@ class BaseName(ViewColumn):
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)
if self.projectedView:
# we need a little more information for the projected view
fitID = self.mainFrame.getActiveFit()
return "%dx %s (%s)" % (stuff.getProjectionInfo(fitID).amount, stuff.name, stuff.ship.item.name)
else:
return "%s (%s)" % (stuff.name, stuff.ship.item.name)
elif isinstance(stuff, Rack):
if service.Fit.getInstance().serviceFittingOptions["rackLabels"]:
if stuff.slot == Slot.MODE:

View File

@@ -19,27 +19,21 @@
from gui.viewColumn import ViewColumn
from gui import bitmapLoader
import gui.mainFrame
import wx
from eos.types import Drone, Module, Rack
from eos.types import Drone, Module, Rack, Fit
from eos.types import State as State_
class State(ViewColumn):
name = "State"
def __init__(self, fittingView, params):
ViewColumn.__init__(self, fittingView)
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.resizable = False
self.size = 16
self.maxsize = self.size
self.mask = wx.LIST_MASK_IMAGE
for name, state in (("checked", wx.CONTROL_CHECKED), ("unchecked", 0)):
bitmap = wx.EmptyBitmap(16, 16)
dc = wx.MemoryDC()
dc.SelectObject(bitmap)
dc.SetBackground(wx.TheBrushList.FindOrCreateBrush(fittingView.GetBackgroundColour(), wx.SOLID))
dc.Clear()
wx.RendererNative.Get().DrawCheckBox(fittingView, dc, wx.Rect(0, 0, 16, 16), state)
dc.Destroy()
setattr(self, "%sId" % name, fittingView.imageList.Add(bitmap))
def getText(self, mod):
return ""
@@ -49,8 +43,14 @@ class State(ViewColumn):
return State_.getName(mod.state).title()
def getImageId(self, stuff):
generic_active = self.fittingView.imageList.GetImageIndex("state_%s_small" % State_.getName(1).lower(), "icons")
generic_inactive = self.fittingView.imageList.GetImageIndex("state_%s_small" % State_.getName(-1).lower(), "icons")
if isinstance(stuff, Drone):
return self.checkedId if stuff.amountActive > 0 else self.uncheckedId
if stuff.amountActive > 0:
return generic_active
else:
return generic_inactive
elif isinstance(stuff, Rack):
return -1
elif isinstance(stuff, Module):
@@ -58,11 +58,21 @@ class State(ViewColumn):
return -1
else:
return self.fittingView.imageList.GetImageIndex("state_%s_small" % State_.getName(stuff.state).lower(), "icons")
elif isinstance(stuff, Fit):
fitID = self.mainFrame.getActiveFit()
projectionInfo = stuff.getProjectionInfo(fitID)
if projectionInfo is None:
return -1
if projectionInfo.active:
return generic_active
return generic_inactive
else:
active = getattr(stuff, "active", None)
if active is None:
return -1
else:
return self.checkedId if active else self.uncheckedId
if active:
return generic_active
return generic_inactive
State.register()

View File

@@ -24,10 +24,11 @@ import bitmapLoader
import sys
import wx.lib.mixins.listctrl as listmix
import wx.html
from eos.types import Ship, Module, Skill, Booster, Implant, Drone, Mode
from eos.types import Fit, Ship, Module, Skill, Booster, Implant, Drone, Mode
from gui.utils.numberFormatter import formatAmount
import service
import config
from gui.contextMenu import ContextMenu
try:
from collections import OrderedDict
@@ -549,15 +550,20 @@ class ItemEffects (wx.Panel):
class ItemAffectedBy (wx.Panel):
ORDER = [Ship, Mode, Module, Drone, Implant, Booster, Skill]
ORDER = [Fit, Ship, Mode, Module, Drone, Implant, Booster, Skill]
def __init__(self, parent, stuff, item):
wx.Panel.__init__ (self, parent)
wx.Panel.__init__(self, parent)
self.stuff = stuff
self.item = item
self.toggleView = 1
self.activeFit = gui.mainFrame.MainFrame.getInstance().getActiveFit()
self.showRealNames = False
self.showAttrView = False
self.expand = -1
self.treeItems = []
mainSizer = wx.BoxSizer(wx.VERTICAL)
self.affectedBy = wx.TreeCtrl(self, style = wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT | wx.NO_BORDER)
@@ -571,7 +577,10 @@ class ItemAffectedBy (wx.Panel):
self.toggleExpandBtn = wx.ToggleButton( self, wx.ID_ANY, u"Expand All", wx.DefaultPosition, wx.DefaultSize, 0 )
bSizer.Add( self.toggleExpandBtn, 0, wx.ALIGN_CENTER_VERTICAL)
self.toggleViewBtn = wx.ToggleButton( self, wx.ID_ANY, u"Toggle view mode", wx.DefaultPosition, wx.DefaultSize, 0 )
self.toggleNameBtn = wx.ToggleButton( self, wx.ID_ANY, u"Toggle Names", wx.DefaultPosition, wx.DefaultSize, 0 )
bSizer.Add( self.toggleNameBtn, 0, wx.ALIGN_CENTER_VERTICAL)
self.toggleViewBtn = wx.ToggleButton( self, wx.ID_ANY, u"Toggle View", wx.DefaultPosition, wx.DefaultSize, 0 )
bSizer.Add( self.toggleViewBtn, 0, wx.ALIGN_CENTER_VERTICAL)
if stuff is not None:
@@ -579,13 +588,36 @@ class ItemAffectedBy (wx.Panel):
bSizer.Add( self.refreshBtn, 0, wx.ALIGN_CENTER_VERTICAL)
self.refreshBtn.Bind( wx.EVT_BUTTON, self.RefreshTree )
self.toggleViewBtn.Bind(wx.EVT_TOGGLEBUTTON,self.ToggleViewMode)
self.toggleNameBtn.Bind(wx.EVT_TOGGLEBUTTON,self.ToggleNameMode)
self.toggleExpandBtn.Bind(wx.EVT_TOGGLEBUTTON,self.ToggleExpand)
self.toggleViewBtn.Bind(wx.EVT_TOGGLEBUTTON,self.ToggleViewMode)
mainSizer.Add( bSizer, 0, wx.ALIGN_RIGHT)
self.SetSizer(mainSizer)
self.PopulateTree()
self.Layout()
self.affectedBy.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.scheduleMenu)
def scheduleMenu(self, event):
event.Skip()
wx.CallAfter(self.spawnMenu, event.Item)
def spawnMenu(self, item):
self.affectedBy.SelectItem(item)
stuff = self.affectedBy.GetPyData(item)
# String is set as data when we are dealing with attributes, not stuff containers
if stuff is None or isinstance(stuff, basestring):
return
contexts = []
# Skills are different in that they don't have itemModifiedAttributes,
# which is needed if we send the container to itemStats dialog. So
# instead, we send the item.
type = stuff.__class__.__name__
contexts.append(("itemStats", type))
menu = ContextMenu.getMenu(stuff if type != "Skill" else stuff.item, *contexts)
self.PopupMenu(menu)
def ExpandCollapseTree(self):
@@ -607,18 +639,11 @@ class ItemAffectedBy (wx.Panel):
def ToggleViewTree(self):
self.Freeze()
root = self.affectedBy.GetRootItem()
child,cookie = self.affectedBy.GetFirstChild(root)
while child.IsOk():
item,childcookie = self.affectedBy.GetFirstChild(child)
while item.IsOk():
change = self.affectedBy.GetPyData(item)
display = self.affectedBy.GetItemText(item)
self.affectedBy.SetItemText(item,change)
self.affectedBy.SetPyData(item,display)
item,childcookie = self.affectedBy.GetNextChild(child,childcookie)
child,cookie = self.affectedBy.GetNextChild(root,cookie)
for item in self.treeItems:
change = self.affectedBy.GetPyData(item)
display = self.affectedBy.GetItemText(item)
self.affectedBy.SetItemText(item, change)
self.affectedBy.SetPyData(item, display)
self.Thaw()
@@ -633,33 +658,214 @@ class ItemAffectedBy (wx.Panel):
event.Skip()
def ToggleViewMode(self, event):
self.toggleView *=-1
self.showAttrView = not self.showAttrView
self.affectedBy.DeleteAllItems()
self.PopulateTree()
event.Skip()
def ToggleNameMode(self, event):
self.showRealNames = not self.showRealNames
self.ToggleViewTree()
event.Skip()
def PopulateTree(self):
# sheri was here
del self.treeItems[:]
root = self.affectedBy.AddRoot("WINPWNZ0R")
self.affectedBy.SetPyData(root, None)
self.imageList = wx.ImageList(16, 16)
self.affectedBy.SetImageList(self.imageList)
cont = self.stuff.itemModifiedAttributes if self.item == self.stuff.item else self.stuff.chargeModifiedAttributes
things = {}
if self.showAttrView:
self.buildAttributeView(root)
else:
self.buildModuleView(root)
for attrName in cont.iterAfflictions():
self.ExpandCollapseTree()
def sortAttrDisplayName(self, attr):
info = self.stuff.item.attributes.get(attr)
if info and info.displayName != "":
return info.displayName
return attr
def buildAttributeView(self, root):
# We first build a usable dictionary of items. The key is either a fit
# if the afflictions stem from a projected fit, or self.stuff if they
# are local afflictions (everything else, even gang boosts at this time)
# The value of this is yet another dictionary in the following format:
#
# "attribute name": {
# "Module Name": [
# class of affliction,
# affliction item (required due to GH issue #335)
# modifier type
# amount of modification
# whether this affliction was projected
# ]
# }
attributes = self.stuff.itemModifiedAttributes if self.item == self.stuff.item else self.stuff.chargeModifiedAttributes
container = {}
for attrName in attributes.iterAfflictions():
# if value is 0 or there has been no change from original to modified, return
if cont[attrName] == (cont.getOriginal(attrName) or 0):
if attributes[attrName] == (attributes.getOriginal(attrName) or 0):
continue
for fit, afflictors in cont.getAfflictions(attrName).iteritems():
for fit, afflictors in attributes.getAfflictions(attrName).iteritems():
for afflictor, modifier, amount, used in afflictors:
if not used or afflictor.item is None:
continue
if afflictor.item.name not in things:
things[afflictor.item.name] = [type(afflictor), set(), []]
if fit.ID != self.activeFit:
# affliction fit does not match our fit
if fit not in container:
container[fit] = {}
items = container[fit]
else:
# local afflictions
if self.stuff not in container:
container[self.stuff] = {}
items = container[self.stuff]
info = things[afflictor.item.name]
# items hold our module: info mappings
if attrName not in items:
items[attrName] = []
if afflictor == self.stuff and getattr(afflictor, 'charge', None):
# we are showing a charges modifications, see #335
item = afflictor.charge
else:
item = afflictor.item
items[attrName].append((type(afflictor), afflictor, item, modifier, amount, getattr(afflictor, "projected", False)))
# Make sure projected fits are on top
rootOrder = container.keys()
rootOrder.sort(key=lambda x: self.ORDER.index(type(x)))
# Now, we take our created dictionary and start adding stuff to our tree
for thing in rootOrder:
# This block simply directs which parent we are adding to (root or projected fit)
if thing == self.stuff:
parent = root
else: # projected fit
icon = self.imageList.Add(bitmapLoader.getBitmap("ship_small", "icons"))
child = self.affectedBy.AppendItem(root, "{} ({})".format(thing.name, thing.ship.item.name), icon)
parent = child
attributes = container[thing]
attrOrder = sorted(attributes.keys(), key=self.sortAttrDisplayName)
for attrName in attrOrder:
attrInfo = self.stuff.item.attributes.get(attrName)
displayName = attrInfo.displayName if attrInfo and attrInfo.displayName != "" else attrName
if attrInfo:
if attrInfo.icon is not None:
iconFile = attrInfo.icon.iconFile
icon = bitmapLoader.getBitmap(iconFile, "pack")
if icon is None:
icon = bitmapLoader.getBitmap("transparent16x16", "icons")
attrIcon = self.imageList.Add(icon)
else:
attrIcon = self.imageList.Add(bitmapLoader.getBitmap("07_15", "pack"))
else:
attrIcon = self.imageList.Add(bitmapLoader.getBitmap("07_15", "pack"))
if self.showRealNames:
display = attrName
saved = displayName
else:
display = displayName
saved = attrName
# this is the attribute node
child = self.affectedBy.AppendItem(parent, display, attrIcon)
self.affectedBy.SetPyData(child, saved)
self.treeItems.append(child)
items = attributes[attrName]
items.sort(key=lambda x: self.ORDER.index(x[0]))
for itemInfo in items:
afflictorType, afflictor, item, attrModifier, attrAmount, projected = itemInfo
if afflictorType == Ship:
itemIcon = self.imageList.Add(bitmapLoader.getBitmap("ship_small", "icons"))
elif item.icon:
bitmap = bitmapLoader.getBitmap(item.icon.iconFile, "pack")
itemIcon = self.imageList.Add(bitmap) if bitmap else -1
else:
itemIcon = -1
displayStr = item.name
if projected:
displayStr += " (projected)"
if attrModifier == "s*":
attrModifier = "*"
penalized = "(penalized)"
else:
penalized = ""
# this is the Module node, the attribute will be attached to this
display = "%s %s %.2f %s" % (displayStr, attrModifier, attrAmount, penalized)
treeItem = self.affectedBy.AppendItem(child, display, itemIcon)
self.affectedBy.SetPyData(treeItem, afflictor)
def buildModuleView(self, root):
# We first build a usable dictionary of items. The key is either a fit
# if the afflictions stem from a projected fit, or self.stuff if they
# are local afflictions (everything else, even gang boosts at this time)
# The value of this is yet another dictionary in the following format:
#
# "Module Name": [
# class of affliction,
# set of afflictors (such as 2 of the same module),
# info on affliction (attribute name, modifier, and modification amount),
# item that will be used to determine icon (required due to GH issue #335)
# whether this affliction is actually used (unlearned skills are not used)
# ]
attributes = self.stuff.itemModifiedAttributes if self.item == self.stuff.item else self.stuff.chargeModifiedAttributes
container = {}
for attrName in attributes.iterAfflictions():
# if value is 0 or there has been no change from original to modified, return
if attributes[attrName] == (attributes.getOriginal(attrName) or 0):
continue
for fit, afflictors in attributes.getAfflictions(attrName).iteritems():
for afflictor, modifier, amount, used in afflictors:
if not used or getattr(afflictor, 'item', None) is None:
continue
if fit.ID != self.activeFit:
# affliction fit does not match our fit
if fit not in container:
container[fit] = {}
items = container[fit]
else:
# local afflictions
if self.stuff not in container:
container[self.stuff] = {}
items = container[self.stuff]
if afflictor == self.stuff and getattr(afflictor, 'charge', None):
# we are showing a charges modifications, see #335
item = afflictor.charge
else:
item = afflictor.item
# items hold our module: info mappings
if item.name not in items:
items[item.name] = [type(afflictor), set(), [], item, getattr(afflictor, "projected", False)]
info = items[item.name]
info[1].add(afflictor)
# If info[1] > 1, there are two separate modules working.
# Check to make sure we only include the modifier once
@@ -668,63 +874,86 @@ class ItemAffectedBy (wx.Panel):
continue
info[2].append((attrName, modifier, amount))
order = things.keys()
order.sort(key=lambda x: (self.ORDER.index(things[x][0]), x))
# Make sure projected fits are on top
rootOrder = container.keys()
rootOrder.sort(key=lambda x: self.ORDER.index(type(x)))
for itemName in order:
info = things[itemName]
# Now, we take our created dictionary and start adding stuff to our tree
for thing in rootOrder:
# This block simply directs which parent we are adding to (root or projected fit)
if thing == self.stuff:
parent = root
else: # projected fit
icon = self.imageList.Add(bitmapLoader.getBitmap("ship_small", "icons"))
child = self.affectedBy.AppendItem(root, "{} ({})".format(thing.name, thing.ship.item.name), icon)
parent = child
afflictorType, afflictors, attrData = info
counter = len(afflictors)
items = container[thing]
order = items.keys()
order.sort(key=lambda x: (self.ORDER.index(items[x][0]), x))
baseAfflictor = afflictors.pop()
if afflictorType == Ship:
itemIcon = self.imageList.Add(bitmapLoader.getBitmap("ship_small", "icons"))
elif baseAfflictor.item.icon:
bitmap = bitmapLoader.getBitmap(baseAfflictor.item.icon.iconFile, "pack")
itemIcon = self.imageList.Add(bitmap) if bitmap else -1
else:
itemIcon = -1
for itemName in order:
info = items[itemName]
afflictorType, afflictors, attrData, item, projected = info
counter = len(afflictors)
if afflictorType == Ship:
itemIcon = self.imageList.Add(bitmapLoader.getBitmap("ship_small", "icons"))
elif item.icon:
bitmap = bitmapLoader.getBitmap(item.icon.iconFile, "pack")
itemIcon = self.imageList.Add(bitmap) if bitmap else -1
else:
itemIcon = -1
child = self.affectedBy.AppendItem(root, "%s" % itemName if counter == 1 else "%s x %d" % (itemName,counter), itemIcon)
displayStr = itemName
if counter > 0:
attributes = []
for attrName, attrModifier, attrAmount in attrData:
attrInfo = self.stuff.item.attributes.get(attrName)
displayName = attrInfo.displayName if attrInfo else ""
if counter > 1:
displayStr += " x {}".format(counter)
if attrInfo:
if attrInfo.icon is not None:
iconFile = attrInfo.icon.iconFile
icon = bitmapLoader.getBitmap(iconFile, "pack")
if icon is None:
icon = bitmapLoader.getBitmap("transparent16x16", "icons")
if projected:
displayStr += " (projected)"
attrIcon = self.imageList.Add(icon)
# this is the Module node, the attribute will be attached to this
child = self.affectedBy.AppendItem(parent, displayStr, itemIcon)
self.affectedBy.SetPyData(child, afflictors.pop())
if counter > 0:
attributes = []
for attrName, attrModifier, attrAmount in attrData:
attrInfo = self.stuff.item.attributes.get(attrName)
displayName = attrInfo.displayName if attrInfo else ""
if attrInfo:
if attrInfo.icon is not None:
iconFile = attrInfo.icon.iconFile
icon = bitmapLoader.getBitmap(iconFile, "pack")
if icon is None:
icon = bitmapLoader.getBitmap("transparent16x16", "icons")
attrIcon = self.imageList.Add(icon)
else:
attrIcon = self.imageList.Add(bitmapLoader.getBitmap("07_15", "pack"))
else:
attrIcon = self.imageList.Add(bitmapLoader.getBitmap("07_15", "pack"))
else:
attrIcon = self.imageList.Add(bitmapLoader.getBitmap("07_15", "pack"))
if attrModifier == "s*":
attrModifier = "*"
penalized = "(penalized)"
else:
penalized = ""
if attrModifier == "s*":
attrModifier = "*"
penalized = "(penalized)"
else:
penalized = ""
attributes.append((attrName, (displayName if displayName != "" else attrName), attrModifier, attrAmount, penalized, attrIcon))
attributes.append((attrName, (displayName if displayName != "" else attrName), attrModifier, attrAmount, penalized, attrIcon))
attrSorted = sorted(attributes, key = lambda attribName: attribName[0])
attrSorted = sorted(attributes, key = lambda attribName: attribName[0])
for attr in attrSorted:
attrName, displayName, attrModifier, attrAmount, penalized, attrIcon = attr
for attr in attrSorted:
attrName, displayName, attrModifier, attrAmount, penalized, attrIcon = attr
if self.toggleView == 1:
treeitem = self.affectedBy.AppendItem(child, "%s %s %.2f %s" % ((displayName if displayName != "" else attrName), attrModifier, attrAmount, penalized), attrIcon)
self.affectedBy.SetPyData(treeitem,"%s %s %.2f %s" % (attrName, attrModifier, attrAmount, penalized))
else:
treeitem = self.affectedBy.AppendItem(child, "%s %s %.2f %s" % (attrName, attrModifier, attrAmount, penalized), attrIcon)
self.affectedBy.SetPyData(treeitem,"%s %s %.2f %s" % ((displayName if displayName != "" else attrName), attrModifier, attrAmount, penalized))
self.ExpandCollapseTree()
if self.showRealNames:
display = "%s %s %.2f %s" % (attrName, attrModifier, attrAmount, penalized)
saved = "%s %s %.2f %s" % ((displayName if displayName != "" else attrName), attrModifier, attrAmount, penalized)
else:
display = "%s %s %.2f %s" % ((displayName if displayName != "" else attrName), attrModifier, attrAmount, penalized)
saved = "%s %s %.2f %s" % (attrName, attrModifier, attrAmount, penalized)
treeitem = self.affectedBy.AppendItem(child, display, attrIcon)
self.affectedBy.SetPyData(treeitem, saved)
self.treeItems.append(treeitem)

View File

@@ -36,7 +36,7 @@ from service.fleet import Fleet
from service.settings import SettingsProvider
from service.port import Port
logger = logging.getLogger("pyfa.service.fit")
logger = logging.getLogger(__name__)
class FitBackupThread(threading.Thread):
def __init__(self, path, callback):
@@ -175,10 +175,14 @@ class Fit(object):
fit = eos.db.getFit(fitID)
sFleet = Fleet.getInstance()
sFleet.removeAssociatedFleetData(fit)
self.removeProjectedData(fitID)
eos.db.remove(fit)
# refresh any fits this fit is projected onto. Otherwise, if we have
# already loaded those fits, they will not reflect the changes
for projection in fit.projectedOnto.values():
eos.db.saveddata_session.refresh(projection.victim_fit)
def copyFit(self, fitID):
fit = eos.db.getFit(fitID)
newFit = copy.deepcopy(fit)
@@ -193,14 +197,6 @@ class Fit(object):
fit.clear()
return fit
def removeProjectedData(self, fitID):
"""Removes projection relation from ships that have fitID as projection. See GitHub issue #90"""
fit = eos.db.getFit(fitID)
fits = eos.db.getProjectedFits(fitID)
for projectee in fits:
projectee.projectedFits.remove(fit)
def toggleFactorReload(self, fitID):
if fitID is None:
return None
@@ -236,6 +232,7 @@ class Fit(object):
return None
fit = eos.db.getFit(fitID)
inited = getattr(fit, "inited", None)
if inited is None or inited is False:
sFleet = Fleet.getInstance()
f = sFleet.getLinearFleet(fit)
@@ -246,6 +243,7 @@ class Fit(object):
fit.fleet = f
if not projected:
print "Not projected, getting projected fits"
for fitP in fit.projectedFits:
self.getFit(fitP.ID, projected = True)
self.recalc(fit, withBoosters=True)
@@ -322,9 +320,14 @@ class Fit(object):
eager=("attributes", "group.category"))
if isinstance(thing, eos.types.Fit):
if thing.ID == fitID:
if thing in fit.projectedFits:
return
fit.projectedFits.append(thing)
fit.__projectedFits[thing.ID] = thing
# this bit is required -- see GH issue # 83
eos.db.saveddata_session.flush()
eos.db.saveddata_session.refresh(thing)
elif thing.category.name == "Drone":
drone = None
for d in fit.projectedDrones.find(thing):
@@ -363,6 +366,22 @@ class Fit(object):
thing.state = self.__getProposedState(thing, click)
if not thing.canHaveState(thing.state, fit):
thing.state = State.OFFLINE
elif isinstance(thing, eos.types.Fit):
print "toggle fit"
projectionInfo = thing.getProjectionInfo(fitID)
if projectionInfo:
projectionInfo.active = not projectionInfo.active
eos.db.commit()
self.recalc(fit)
def changeAmount(self, fitID, projected_fit, amount):
"""Change amount of projected fits"""
fit = eos.db.getFit(fitID)
amount = min(20, max(1, amount)) # 1 <= a <= 20
projectionInfo = projected_fit.getProjectionInfo(fitID)
if projectionInfo:
projectionInfo.amount = amount
eos.db.commit()
self.recalc(fit)
@@ -374,7 +393,8 @@ class Fit(object):
elif isinstance(thing, eos.types.Module):
fit.projectedModules.remove(thing)
else:
fit.projectedFits.remove(thing)
del fit.__projectedFits[thing.ID]
#fit.projectedFits.remove(thing)
eos.db.commit()
self.recalc(fit)
@@ -921,8 +941,9 @@ class Fit(object):
eos.db.commit()
self.recalc(fit)
def recalc(self, fit, withBoosters=False):
def recalc(self, fit, withBoosters=True):
logger.debug("="*10+"recalc"+"="*10)
if fit.factorReload is not self.serviceFittingOptions["useGlobalForceReload"]:
fit.factorReload = self.serviceFittingOptions["useGlobalForceReload"]
fit.clear()
fit.calculateModifiedAttributes(withBoosters=withBoosters, dirtyStorage=self.dirtyFitIDs)
fit.clear()
fit.calculateModifiedAttributes(withBoosters=withBoosters)

View File

@@ -1,30 +1,36 @@
import time
class Timer(object):
"""
Generic timing class for simple profiling.
class Timer():
def __init__(self, name='', logger=None):
self.name = name
self.start = time.time()
self.__last = self.start
self.logger = logger
Usage:
@property
def elapsed(self):
return (time.time() - self.start)*1000
with Timer(verbose=True) as t:
# code to be timed
time.sleep(5)
@property
def last(self):
return (time.time() - self.__last)*1000
Output:
elapsed time: 5000.000 ms
Can also access time with t.secs
"""
def __init__(self, verbose=False):
self.verbose = verbose
def checkpoint(self, name=''):
text = 'Timer - {timer} - {checkpoint} - {last:.2f}ms ({elapsed:.2f}ms elapsed)'.format(
timer=self.name,
checkpoint=name,
last=self.last,
elapsed=self.elapsed
).strip()
self.__last = time.time()
if self.logger:
self.logger.debug(text)
else:
print text
def __enter__(self):
self.start = time.time()
return self
def __exit__(self, *args):
self.end = time.time()
self.secs = self.end - self.start
self.msecs = self.secs * 1000 # millisecs
if self.verbose:
print 'elapsed time: %f ms' % self.msecs
def __exit__(self, type, value, traceback):
self.checkpoint('finished')
pass