Merge remote-tracking branch 'origin/master' into attrGroup

# Conflicts:
#	eve.db
This commit is contained in:
blitzmann
2019-02-28 18:51:52 -05:00
1371 changed files with 2257 additions and 1148 deletions

View File

@@ -19,7 +19,7 @@
import config
versionString = "{0} {1} - {2} {3}".format(config.version, config.tag, config.expansionName, config.expansionVersion)
versionString = "{0}".format(config.version)
licenses = (
"pyfa is released under GNU GPLv3 - see included LICENSE file",
"All EVE-Online related materials are property of CCP hf.",

View File

@@ -4,6 +4,7 @@ import wx
from service.fit import Fit
import gui.globalEvents as GE
import gui.mainFrame
from gui.utils.helpers_wxPython import HandleCtrlBackspace
class NotesView(wx.Panel):
@@ -17,9 +18,16 @@ class NotesView(wx.Panel):
self.SetSizer(mainSizer)
self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged)
self.Bind(wx.EVT_TEXT, self.onText)
self.editNotes.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
self.saveTimer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.delayedSave, self.saveTimer)
def OnKeyDown(self, event):
if event.RawControlDown() and event.GetKeyCode() == wx.WXK_BACK:
HandleCtrlBackspace(self.editNotes)
else:
event.Skip()
def fitChanged(self, event):
sFit = Fit.getInstance()
fit = sFit.getFit(event.fitID)

View File

@@ -0,0 +1,34 @@
from gui.contextMenu import ContextMenu
import gui.mainFrame
# noinspection PyPackageRequirements
import wx
import gui.globalEvents as GE
from service.settings import ContextMenuSettings
import gui.fitCommands as cmd
class FillWithModule(ContextMenu):
def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, srcContext, selection):
if not self.settings.get('moduleFill'):
return False
return srcContext in ("fittingModule")
def getText(self, itmContext, selection):
return u"Fill With {0}".format(itmContext if itmContext is not None else "Module")
def activate(self, fullContext, selection, i):
srcContext = fullContext[0]
fitID = self.mainFrame.getActiveFit()
if srcContext == "fittingModule":
self.mainFrame.command.Submit(cmd.GuiFillWithModuleCommand(fitID, selection[0].itemID))
return # the command takes care of the PostEvent
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
FillWithModule.register()

View File

@@ -26,7 +26,10 @@ class MarketJump(ContextMenu):
sMkt = Market.getInstance()
item = getattr(selection[0], "item", selection[0])
isMutated = getattr(selection[0], "isMutated", False)
mktGrp = sMkt.getMarketGroupByItem(item)
if mktGrp is None and isMutated:
mktGrp = sMkt.getMarketGroupByItem(selection[0].baseItem)
# 1663 is Special Edition Festival Assets, we don't have root group for it
if mktGrp is None or mktGrp.ID == 1663:
@@ -43,7 +46,10 @@ class MarketJump(ContextMenu):
if srcContext in ("fittingCharge", "projectedCharge"):
item = selection[0].charge
elif hasattr(selection[0], "item"):
item = selection[0].item
if getattr(selection[0], "isMutated", False):
item = selection[0].baseItem
else:
item = selection[0].item
else:
item = selection[0]

View File

@@ -1,7 +1,10 @@
import math
import wx
import wx.lib.newevent
from gui.attribute_gauge import AttributeGauge
from eos.utils.float import floatUnerr
_ValueChanged, EVT_VALUE_CHANGED = wx.lib.newevent.NewEvent()
@@ -58,22 +61,41 @@ class AttributeSlider(wx.Panel):
self.inverse = inverse
# The internal slider basically represents the percentage towards the end of the range. It has to be normalized
# in this way, otherwise when we start off with a base, if the range is skewed to one side, the base value won't
# be centered. We use a range of -100,100 so that we can depend on the SliderValue to contain the percentage
# toward one end
def getStep(valRange):
"""
Find step for the passed range, which is based on 1, 2 or 5.
Step returned will make sure that range fits 10..50 of them,
as close to 10 as possible.
"""
steps = {1: None, 2: None, 5: None}
for baseInc in steps:
baseIncAmount = valRange / baseInc
incScale = math.floor(math.log10(baseIncAmount) - 1)
steps[baseInc] = baseInc * 10 ** incScale
chosenBase = min(steps, key=lambda base: valRange / steps[base])
chosenStep = steps[chosenBase]
if inverse:
chosenStep *= -1
return chosenStep
# Additionally, since we want the slider to be accurate to 3 decimal places, we need to blow out the two ends here
# (if we have a slider that needs to land on 66.66% towards the right, it will actually be converted to 66%. Se we need it to support 6,666)
#
# self.SliderMinValue = -100
# self.SliderMaxValue = 100
# self.SliderValue = 0
def getDigitPlaces(minValue, maxValue):
minDigits = 3
maxDigits = 5
currentDecision = minDigits
for value in (floatUnerr(minValue), floatUnerr(maxValue)):
for currentDigit in range(minDigits, maxDigits + 1):
if round(value, currentDigit) == value:
if currentDigit > currentDecision:
currentDecision = currentDigit
break
# Max decimal places we can afford to show was not enough
else:
return maxDigits
return currentDecision
range = [self.UserMinValue, self.UserMaxValue]
self.ctrl = wx.SpinCtrlDouble(self, min=minValue, max=maxValue, inc=getStep(maxValue - minValue))
self.ctrl.SetDigits(getDigitPlaces(minValue, maxValue))
self.ctrl = wx.SpinCtrlDouble(self, min=min(range), max=max(range))
self.ctrl.SetDigits(3)
self.ctrl.Bind(wx.EVT_SPINCTRLDOUBLE, self.UpdateValue)

View File

@@ -8,6 +8,10 @@ from service.attribute import Attribute
from gui.utils.numberFormatter import formatAmount
def defaultSort(item):
return (item.attributes['metaLevel'].value if 'metaLevel' in item.attributes else 0, item.name)
class ItemCompare(wx.Panel):
def __init__(self, parent, stuff, item, items, context=None):
# Start dealing with Price stuff to get that thread going
@@ -27,8 +31,7 @@ class ItemCompare(wx.Panel):
self.currentSort = None
self.sortReverse = False
self.item = item
self.items = sorted(items,
key=lambda x: x.attributes['metaLevel'].value if 'metaLevel' in x.attributes else 0)
self.items = sorted(items, key=defaultSort)
self.attrs = {}
# get a dict of attrName: attrInfo of all unique attributes across all items
@@ -126,10 +129,15 @@ class ItemCompare(wx.Panel):
# starts at 0 while the list has the item name as column 0.
attr = str(list(self.attrs.keys())[sort - 1])
func = lambda _val: _val.attributes[attr].value if attr in _val.attributes else 0.0
# Clicked on a column that's not part of our array (price most likely)
except IndexError:
# Clicked on a column that's not part of our array (price most likely)
self.sortReverse = False
func = lambda _val: _val.attributes['metaLevel'].value if 'metaLevel' in _val.attributes else 0.0
# Price
if sort == len(self.attrs) + 1:
func = lambda i: i.price.price if i.price.price != 0 else float("Inf")
# Something else
else:
self.sortReverse = False
func = defaultSort
self.items = sorted(self.items, key=func, reverse=self.sortReverse)
@@ -160,7 +168,7 @@ class ItemCompare(wx.Panel):
self.paramList.SetItem(i, x + 1, valueUnit)
# Add prices
self.paramList.SetItem(i, len(self.attrs) + 1, formatAmount(item.price.price, 3, 3, 9, currency=True))
self.paramList.SetItem(i, len(self.attrs) + 1, formatAmount(item.price.price, 3, 3, 9, currency=True) if item.price.price else "")
self.paramList.RefreshRows()
self.Layout()

View File

@@ -15,7 +15,6 @@ class ItemDescription(wx.Panel):
fgcolor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)
self.description = wx.html.HtmlWindow(self)
if not item.description:
return
@@ -24,8 +23,11 @@ class ItemDescription(wx.Panel):
desc = re.sub("<( *)font( *)color( *)=(.*?)>(?P<inside>.*?)<( *)/( *)font( *)>", "\g<inside>", desc)
# Strip URLs
desc = re.sub("<( *)a(.*?)>(?P<inside>.*?)<( *)/( *)a( *)>", "\g<inside>", desc)
desc = "<body bgcolor='" + bgcolor.GetAsString(wx.C2S_HTML_SYNTAX) + "' text='" + fgcolor.GetAsString(
wx.C2S_HTML_SYNTAX) + "' >" + desc + "</body>"
desc = "<body style='background-color: {}; color: {}'>{}</body>".format(
bgcolor.GetAsString(wx.C2S_CSS_SYNTAX),
fgcolor.GetAsString(wx.C2S_CSS_SYNTAX),
desc
)
self.description.SetPage(desc)

View File

@@ -22,38 +22,67 @@ class ItemMutator(wx.Panel):
self.item = item
self.timer = None
self.activeFit = gui.mainFrame.MainFrame.getInstance().getActiveFit()
font = parent.GetFont()
font.SetWeight(wx.BOLD)
mainSizer = wx.BoxSizer(wx.VERTICAL)
sourceItemsSizer = wx.BoxSizer(wx.HORIZONTAL)
sourceItemsSizer.Add(BitmapLoader.getStaticBitmap(stuff.item.iconID, self, "icons"), 0, wx.LEFT, 5)
sourceItemsSizer.Add(BitmapLoader.getStaticBitmap(stuff.mutaplasmid.item.iconID, self, "icons"), 0, wx.LEFT, 0)
sourceItemShort = "{} {}".format(stuff.mutaplasmid.item.name.split(" ")[0], stuff.baseItem.name)
sourceItemText = wx.StaticText(self, wx.ID_ANY, sourceItemShort)
sourceItemText.SetFont(font)
sourceItemsSizer.Add(sourceItemText, 0, wx.LEFT, 10)
mainSizer.Add(sourceItemsSizer, 0, wx.TOP | wx.EXPAND, 10)
mainSizer.Add(wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0, wx.ALL | wx.EXPAND, 5)
self.goodColor = wx.Colour(96, 191, 0)
self.badColor = wx.Colour(255, 64, 0)
self.event_mapping = {}
for m in sorted(stuff.mutators.values(), key=lambda x: x.attribute.displayName):
# create array for the two ranges
min_t = [m.minValue, m.minMod, None]
max_t = [m.maxValue, m.maxMod, None]
# Format: [raw value, modifier applied to base raw value, display value]
range1 = (m.minValue, m.attribute.unit.SimplifyValue(m.minValue))
range2 = (m.maxValue, m.attribute.unit.SimplifyValue(m.maxValue))
# Then we need to determine if it's better than original, which will be the color
min_t[2] = min_t[1] < 1 if not m.highIsGood else 1 < min_t[1]
max_t[2] = max_t[1] < 1 if not m.highIsGood else 1 < max_t[1]
# Lastly, we need to determine which range value is "worse" (left side) or "better" (right side)
if (m.highIsGood and min_t[1] > max_t[1]) or (not m.highIsGood and min_t[1] < max_t[1]):
better_range = min_t
# minValue/maxValue do not always correspond to min/max, because these are
# just base value multiplied by minMod/maxMod, and in case base is negative
# minValue is actually bigger than maxValue
if range1[0] <= range2[0]:
minRange = range1
maxRange = range2
else:
better_range = max_t
minRange = range2
maxRange = range1
if (m.highIsGood and max_t[1] < min_t[1]) or (not m.highIsGood and max_t[1] > min_t[1]):
worse_range = max_t
if (m.highIsGood and minRange[0] >= maxRange[0]) or (not m.highIsGood and minRange[0] <= maxRange[0]):
betterRange = minRange
worseRange = maxRange
else:
worse_range = min_t
betterRange = maxRange
worseRange = minRange
if minRange[1] >= maxRange[1]:
displayMaxRange = minRange
displayMinRange = maxRange
else:
displayMaxRange = maxRange
displayMinRange = minRange
# If base value is outside of mutation range, make sure that center of slider
# corresponds to the value which is closest available to actual base value. It's
# how EVE handles it
if minRange[0] <= m.baseValue <= maxRange[0]:
sliderBaseValue = m.baseValue
else:
sliderBaseValue = max(minRange[0], min(maxRange[0], m.baseValue))
headingSizer = wx.BoxSizer(wx.HORIZONTAL)
font = parent.GetFont()
font.SetWeight(wx.BOLD)
headingSizer.Add(BitmapLoader.getStaticBitmap(m.attribute.iconID, self, "icons"), 0, wx.RIGHT, 10)
displayName = wx.StaticText(self, wx.ID_ANY, m.attribute.displayName)
@@ -61,25 +90,25 @@ class ItemMutator(wx.Panel):
headingSizer.Add(displayName, 3, wx.ALL | wx.EXPAND, 0)
worst_val = ItemParams.FormatValue(*m.attribute.unit.PreformatValue(worse_range[0]), rounding='dec')
worst_text = wx.StaticText(self, wx.ID_ANY, worst_val)
worst_text.SetForegroundColour(self.goodColor if worse_range[2] else self.badColor)
worseVal = ItemParams.FormatValue(*m.attribute.unit.PreformatValue(worseRange[0]), rounding='dec')
worseText = wx.StaticText(self, wx.ID_ANY, worseVal)
worseText.SetForegroundColour(self.badColor)
best_val = ItemParams.FormatValue(*m.attribute.unit.PreformatValue(better_range[0]), rounding='dec')
best_text = wx.StaticText(self, wx.ID_ANY, best_val)
best_text.SetForegroundColour(self.goodColor if better_range[2] else self.badColor)
betterVal = ItemParams.FormatValue(*m.attribute.unit.PreformatValue(betterRange[0]), rounding='dec')
betterText = wx.StaticText(self, wx.ID_ANY, betterVal)
betterText.SetForegroundColour(self.goodColor)
headingSizer.Add(worst_text, 0, wx.ALL | wx.EXPAND, 0)
headingSizer.Add(worseText, 0, wx.ALL | wx.EXPAND, 0)
headingSizer.Add(wx.StaticText(self, wx.ID_ANY, ""), 0, wx.RIGHT | wx.LEFT | wx.EXPAND, 5)
headingSizer.Add(best_text, 0, wx.RIGHT | wx.EXPAND, 10)
headingSizer.Add(betterText, 0, wx.RIGHT | wx.EXPAND, 10)
mainSizer.Add(headingSizer, 0, wx.ALL | wx.EXPAND, 5)
slider = AttributeSlider(parent=self,
baseValue=m.attribute.unit.SimplifyValue(m.baseValue),
minValue=m.attribute.unit.SimplifyValue(min_t[0]),
maxValue=m.attribute.unit.SimplifyValue(max_t[0]),
inverse=better_range is min_t)
baseValue=m.attribute.unit.SimplifyValue(sliderBaseValue),
minValue=displayMinRange[1],
maxValue=displayMaxRange[1],
inverse=displayMaxRange is worseRange)
slider.SetValue(m.attribute.unit.SimplifyValue(m.value), False)
slider.Bind(EVT_VALUE_CHANGED, self.changeMutatedValue)
self.event_mapping[slider] = m

View File

@@ -2,6 +2,7 @@
import wx
import gui.utils.color as colorUtils
import gui.utils.draw as drawUtils
from gui.utils.helpers_wxPython import HandleCtrlBackspace
SearchButton, EVT_SEARCH_BTN = wx.lib.newevent.NewEvent()
CancelButton, EVT_CANCEL_BTN = wx.lib.newevent.NewEvent()
@@ -55,7 +56,7 @@ class PFSearchBox(wx.Window):
self.EditBox.Bind(wx.EVT_SET_FOCUS, self.OnEditSetFocus)
self.EditBox.Bind(wx.EVT_KILL_FOCUS, self.OnEditKillFocus)
self.EditBox.Bind(wx.EVT_KEY_DOWN, self.OnKeyPress)
self.EditBox.Bind(wx.EVT_TEXT, self.OnText)
self.EditBox.Bind(wx.EVT_TEXT_ENTER, self.OnTextEnter)
@@ -83,6 +84,12 @@ class PFSearchBox(wx.Window):
self.Clear()
event.Skip()
def OnKeyPress(self, event):
if event.RawControlDown() and event.GetKeyCode() == wx.WXK_BACK:
HandleCtrlBackspace(self.EditBox)
else:
event.Skip()
def Clear(self):
self.EditBox.Clear()
# self.EditBox.ChangeValue(self.descriptiveText)

View File

@@ -81,6 +81,11 @@ class PFContextMenuPref(PreferenceView):
rbSizerRow3.Add(self.rbBox7, 1, wx.TOP | wx.RIGHT, 5)
self.rbBox7.Bind(wx.EVT_RADIOBOX, self.OnSetting7Change)
self.rbBox8 = wx.RadioBox(panel, -1, "Fill with module", wx.DefaultPosition, wx.DefaultSize, ['Disabled', 'Enabled'], 1, wx.RA_SPECIFY_COLS)
self.rbBox8.SetSelection(self.settings.get('moduleFill'))
rbSizerRow3.Add(self.rbBox8, 1, wx.TOP | wx.RIGHT, 5)
self.rbBox8.Bind(wx.EVT_RADIOBOX, self.OnSetting8Change)
mainSizer.Add(rbSizerRow3, 1, wx.ALL | wx.EXPAND, 0)
panel.SetSizer(mainSizer)
@@ -107,6 +112,9 @@ class PFContextMenuPref(PreferenceView):
def OnSetting7Change(self, event):
self.settings.set('project', event.GetInt())
def OnSetting8Change(self, event):
self.settings.set('moduleFill', event.GetInt())
def getImage(self):
return BitmapLoader.getBitmap("settings_menu", "gui")

View File

@@ -76,10 +76,6 @@ class PFGeneralPref(PreferenceView):
self.cbGaugeAnimation = wx.CheckBox(panel, wx.ID_ANY, "Animate gauges", wx.DefaultPosition, wx.DefaultSize, 0)
mainSizer.Add(self.cbGaugeAnimation, 0, wx.ALL | wx.EXPAND, 5)
self.cbExportCharges = wx.CheckBox(panel, wx.ID_ANY, "Export loaded charges", wx.DefaultPosition,
wx.DefaultSize, 0)
mainSizer.Add(self.cbExportCharges, 0, wx.ALL | wx.EXPAND, 5)
self.cbOpenFitInNew = wx.CheckBox(panel, wx.ID_ANY, "Open fittings in a new page by default",
wx.DefaultPosition, wx.DefaultSize, 0)
mainSizer.Add(self.cbOpenFitInNew, 0, wx.ALL | wx.EXPAND, 5)
@@ -133,7 +129,6 @@ class PFGeneralPref(PreferenceView):
self.cbShowTooltip.SetValue(self.sFit.serviceFittingOptions["showTooltip"] or False)
self.cbMarketShortcuts.SetValue(self.sFit.serviceFittingOptions["showMarketShortcuts"] or False)
self.cbGaugeAnimation.SetValue(self.sFit.serviceFittingOptions["enableGaugeAnimation"])
self.cbExportCharges.SetValue(self.sFit.serviceFittingOptions["exportCharges"])
self.cbOpenFitInNew.SetValue(self.sFit.serviceFittingOptions["openFitInNew"])
self.chPriceSource.SetStringSelection(self.sFit.serviceFittingOptions["priceSource"])
self.chPriceSystem.SetStringSelection(self.sFit.serviceFittingOptions["priceSystem"])
@@ -151,7 +146,6 @@ class PFGeneralPref(PreferenceView):
self.cbShowTooltip.Bind(wx.EVT_CHECKBOX, self.onCBShowTooltip)
self.cbMarketShortcuts.Bind(wx.EVT_CHECKBOX, self.onCBShowShortcuts)
self.cbGaugeAnimation.Bind(wx.EVT_CHECKBOX, self.onCBGaugeAnimation)
self.cbExportCharges.Bind(wx.EVT_CHECKBOX, self.onCBExportCharges)
self.cbOpenFitInNew.Bind(wx.EVT_CHECKBOX, self.onCBOpenFitInNew)
self.chPriceSource.Bind(wx.EVT_CHOICE, self.onPricesSourceSelection)
self.chPriceSystem.Bind(wx.EVT_CHOICE, self.onPriceSelection)
@@ -220,9 +214,6 @@ class PFGeneralPref(PreferenceView):
def onCBGaugeAnimation(self, event):
self.sFit.serviceFittingOptions["enableGaugeAnimation"] = self.cbGaugeAnimation.GetValue()
def onCBExportCharges(self, event):
self.sFit.serviceFittingOptions["exportCharges"] = self.cbExportCharges.GetValue()
def onCBOpenFitInNew(self, event):
self.sFit.serviceFittingOptions["openFitInNew"] = self.cbOpenFitInNew.GetValue()

View File

@@ -11,6 +11,7 @@ import gui.utils.fonts as fonts
from .events import FitSelected, SearchSelected, ImportSelected, Stage1Selected, Stage2Selected, Stage3Selected
from gui.bitmap_loader import BitmapLoader
from service.fit import Fit
from gui.utils.helpers_wxPython import HandleCtrlBackspace
pyfalog = Logger(__name__)
@@ -72,7 +73,7 @@ class NavigationPanel(SFItem.SFBrowserItem):
# self.BrowserSearchBox.Bind(wx.EVT_TEXT_ENTER, self.OnBrowserSearchBoxEnter)
# self.BrowserSearchBox.Bind(wx.EVT_KILL_FOCUS, self.OnBrowserSearchBoxLostFocus)
self.BrowserSearchBox.Bind(wx.EVT_KEY_DOWN, self.OnBrowserSearchBoxEsc)
self.BrowserSearchBox.Bind(wx.EVT_KEY_DOWN, self.OnBrowserSearchBoxKeyPress)
self.BrowserSearchBox.Bind(wx.EVT_TEXT, self.OnScheduleSearch)
self.SetMinSize(size)
@@ -103,9 +104,11 @@ class NavigationPanel(SFItem.SFBrowserItem):
def OnBrowserSearchBoxLostFocus(self, event):
self.BrowserSearchBox.Show(False)
def OnBrowserSearchBoxEsc(self, event):
def OnBrowserSearchBoxKeyPress(self, event):
if event.GetKeyCode() == wx.WXK_ESCAPE:
self.BrowserSearchBox.Show(False)
elif event.RawControlDown() and event.GetKeyCode() == wx.WXK_BACK:
HandleCtrlBackspace(self.BrowserSearchBox)
else:
event.Skip()

View File

@@ -22,7 +22,8 @@ import wx
import gui.mainFrame
from gui.statsView import StatsView
from gui.bitmap_loader import BitmapLoader
from gui.utils.numberFormatter import formatAmount
from gui.utils.numberFormatter import formatAmount, roundToPrec
from eos.utils.spoolSupport import SpoolType, SpoolOptions
from service.fit import Fit
@@ -148,27 +149,55 @@ class FirepowerViewFull(StatsView):
else:
self.stEff.Hide()
stats = (("labelFullDpsWeapon", lambda: fit.weaponDPS, 3, 0, 0, "%s DPS", None),
("labelFullDpsDrone", lambda: fit.droneDPS, 3, 0, 0, "%s DPS", None),
("labelFullVolleyTotal", lambda: fit.totalVolley, 3, 0, 0, "%s", "Volley: %.1f"),
("labelFullDpsTotal", lambda: fit.totalDPS, 3, 0, 0, "%s", None))
# See GH issue #
# if fit is not None and fit.totalYield > 0:
# self.miningyield.Show()
# else:
# self.miningyield.Hide()
def dpsToolTip(preSpool, fullSpool, prec, lowest, highest):
if roundToPrec(preSpool, prec) == roundToPrec(fullSpool, prec):
return ""
else:
return "Spool up: {}-{}".format(
formatAmount(preSpool, prec, lowest, highest),
formatAmount(fullSpool, prec, lowest, highest))
# TODO: fetch spoolup option
defaultSpoolValue = 1
stats = (
(
"labelFullDpsWeapon",
lambda: fit.getWeaponDps(spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False)).total,
lambda: fit.getWeaponDps(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).total,
lambda: fit.getWeaponDps(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).total,
3, 0, 0, "{}{} DPS"),
(
"labelFullDpsDrone",
lambda: fit.getDroneDps().total,
lambda: fit.getDroneDps().total,
lambda: fit.getDroneDps().total,
3, 0, 0, "{}{} DPS"),
(
"labelFullVolleyTotal",
lambda: fit.getTotalVolley(spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False)).total,
lambda: fit.getTotalVolley(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).total,
lambda: fit.getTotalVolley(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).total,
3, 0, 0, "{}{}"),
(
"labelFullDpsTotal",
lambda: fit.getTotalDps(spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False)).total,
lambda: fit.getTotalDps(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).total,
lambda: fit.getTotalDps(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).total,
3, 0, 0, "{}{}"))
counter = 0
for labelName, value, prec, lowest, highest, valueFormat, altFormat in stats:
for labelName, val, preSpoolVal, fullSpoolVal, prec, lowest, highest, valueFormat in stats:
label = getattr(self, labelName)
value = value() if fit is not None else 0
value = value if value is not None else 0
if self._cachedValues[counter] != value:
valueStr = formatAmount(value, prec, lowest, highest)
label.SetLabel(valueFormat % valueStr)
tipStr = valueFormat % valueStr if altFormat is None else altFormat % value
label.SetToolTip(wx.ToolTip(tipStr))
self._cachedValues[counter] = value
val = val() if fit is not None else 0
preSpoolVal = preSpoolVal() if fit is not None else 0
fullSpoolVal = fullSpoolVal() if fit is not None else 0
if self._cachedValues[counter] != val:
tooltipText = dpsToolTip(preSpoolVal, fullSpoolVal, prec, lowest, highest)
label.SetLabel(valueFormat.format(
formatAmount(val, prec, lowest, highest),
"\u02e2" if tooltipText else ""))
label.SetToolTip(wx.ToolTip(tooltipText))
self._cachedValues[counter] = val
counter += 1
self.panel.Layout()

View File

@@ -21,7 +21,35 @@
import wx
from gui.statsView import StatsView
from gui.bitmap_loader import BitmapLoader
from gui.utils.numberFormatter import formatAmount
from gui.utils.numberFormatter import formatAmount, roundToPrec
from eos.utils.spoolSupport import SpoolType, SpoolOptions
stats = [
(
"labelRemoteCapacitor", "Capacitor:", "{}{} GJ/s", "capacitorInfo", "Capacitor restored",
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, spool, False)).get("Capacitor", 0),
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).get("Capacitor", 0),
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).get("Capacitor", 0),
3, 0, 0),
(
"labelRemoteShield", "Shield:", "{}{} HP/s", "shieldActive", "Shield restored",
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, spool, False)).get("Shield", 0),
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).get("Shield", 0),
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).get("Shield", 0),
3, 0, 0),
(
"labelRemoteArmor", "Armor:", "{}{} HP/s", "armorActive", "Armor restored",
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, spool, False)).get("Armor", 0),
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).get("Armor", 0),
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).get("Armor", 0),
3, 0, 0),
(
"labelRemoteHull", "Hull:", "{}{} HP/s", "hullActive", "Hull restored",
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, spool, False)).get("Hull", 0),
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).get("Hull", 0),
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).get("Hull", 0),
3, 0, 0)]
class OutgoingViewFull(StatsView):
@@ -48,56 +76,46 @@ class OutgoingViewFull(StatsView):
contentSizer.Add(sizerOutgoing, 0, wx.EXPAND, 0)
counter = 0
rr_list = [
("RemoteCapacitor", "Capacitor:", "capacitorInfo", "Capacitor GJ/s per second transferred remotely."),
("RemoteShield", "Shield:", "shieldActive", "Shield hitpoints per second repaired remotely."),
("RemoteArmor", "Armor:", "armorActive", "Armor hitpoints per second repaired remotely."),
("RemoteHull", "Hull:", "hullActive", "Hull hitpoints per second repaired remotely."),
]
for outgoingType, label, image, tooltip in rr_list:
for labelName, labelDesc, valueFormat, image, tooltip, val, preSpoolVal, fullSpoolVal, prec, lowest, highest in stats:
baseBox = wx.BoxSizer(wx.VERTICAL)
baseBox.Add(BitmapLoader.getStaticBitmap("%s_big" % image, parent, "gui"), 0, wx.ALIGN_CENTER)
if "Capacitor" in outgoingType:
lbl = wx.StaticText(parent, wx.ID_ANY, "0 GJ/s")
else:
lbl = wx.StaticText(parent, wx.ID_ANY, "0 HP/s")
lbl = wx.StaticText(parent, wx.ID_ANY, valueFormat.format(0, ""))
lbl.SetToolTip(wx.ToolTip(tooltip))
setattr(self, "label%s" % outgoingType, lbl)
setattr(self, labelName, lbl)
baseBox.Add(lbl, 0, wx.ALIGN_CENTER)
self._cachedValues.append(0)
counter += 1
sizerOutgoing.Add(baseBox, 1, wx.ALIGN_LEFT)
def refreshPanel(self, fit):
# If we did anything intresting, we'd update our labels to reflect the new fit's stats here
stats = [
("labelRemoteArmor", lambda: fit.remoteReps["Armor"], 3, 0, 0, "%s HP/s", None),
("labelRemoteShield", lambda: fit.remoteReps["Shield"], 3, 0, 0, "%s HP/s", None),
("labelRemoteHull", lambda: fit.remoteReps["Hull"], 3, 0, 0, "%s HP/s", None),
("labelRemoteCapacitor", lambda: fit.remoteReps["Capacitor"], 3, 0, 0, "%s GJ/s", None),
]
def formatTooltip(text, preSpool, fullSpool, prec, lowest, highest):
if roundToPrec(preSpool, prec) == roundToPrec(fullSpool, prec):
return False, text
else:
return True, "{}\nSpool up: {}-{}".format(
text,
formatAmount(preSpool, prec, lowest, highest),
formatAmount(fullSpool, prec, lowest, highest))
# TODO: fetch spoolup option
defaultSpoolValue = 1
counter = 0
for labelName, value, prec, lowest, highest, valueFormat, altFormat in stats:
for labelName, labelDesc, valueFormat, image, tooltip, val, preSpoolVal, fullSpoolVal, prec, lowest, highest in stats:
label = getattr(self, labelName)
value = value() if fit is not None else 0
value = value if value is not None else 0
if self._cachedValues[counter] != value:
valueStr = formatAmount(value, prec, lowest, highest)
label.SetLabel(valueFormat % valueStr)
tipStr = valueFormat % valueStr if altFormat is None else altFormat % value
label.SetToolTip(wx.ToolTip(tipStr))
self._cachedValues[counter] = value
val = val(fit, defaultSpoolValue) if fit is not None else 0
preSpoolVal = preSpoolVal(fit) if fit is not None else 0
fullSpoolVal = fullSpoolVal(fit) if fit is not None else 0
if self._cachedValues[counter] != val:
hasSpool, tooltipText = formatTooltip(tooltip, preSpoolVal, fullSpoolVal, prec, lowest, highest)
label.SetLabel(valueFormat.format(
formatAmount(val, prec, lowest, highest),
"\u02e2" if hasSpool else ""))
label.SetToolTip(wx.ToolTip(tooltipText))
self._cachedValues[counter] = val
counter += 1
self.panel.Layout()
self.headerPanel.Layout()

View File

@@ -20,7 +20,35 @@
# noinspection PyPackageRequirements
import wx
from gui.statsView import StatsView
from gui.utils.numberFormatter import formatAmount
from gui.utils.numberFormatter import formatAmount, roundToPrec
from eos.utils.spoolSupport import SpoolType, SpoolOptions
stats = [
(
"labelRemoteCapacitor", "Capacitor:", "{}{} GJ/s", "capacitorInfo", "Capacitor restored",
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, spool, False)).get("Capacitor", 0),
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).get("Capacitor", 0),
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).get("Capacitor", 0),
3, 0, 0),
(
"labelRemoteShield", "Shield:", "{}{} HP/s", "shieldActive", "Shield restored",
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, spool, False)).get("Shield", 0),
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).get("Shield", 0),
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).get("Shield", 0),
3, 0, 0),
(
"labelRemoteArmor", "Armor:", "{}{} HP/s", "armorActive", "Armor restored",
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, spool, False)).get("Armor", 0),
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).get("Armor", 0),
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).get("Armor", 0),
3, 0, 0),
(
"labelRemoteHull", "Hull:", "{}{} HP/s", "hullActive", "Hull restored",
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, spool, False)).get("Hull", 0),
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).get("Hull", 0),
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).get("Hull", 0),
3, 0, 0)]
class OutgoingViewMinimal(StatsView):
@@ -47,56 +75,46 @@ class OutgoingViewMinimal(StatsView):
contentSizer.Add(sizerOutgoing, 0, wx.EXPAND, 0)
counter = 0
rr_list = [
("RemoteCapacitor", "Capacitor:", "capacitorInfo", "Capacitor GJ/s per second transferred remotely."),
("RemoteShield", "Shield:", "shieldActive", "Shield hitpoints per second repaired remotely."),
("RemoteArmor", "Armor:", "armorActive", "Armor hitpoints per second repaired remotely."),
("RemoteHull", "Hull:", "hullActive", "Hull hitpoints per second repaired remotely."),
]
for outgoingType, label, image, tooltip in rr_list:
for labelName, labelDesc, valueFormat, image, tooltip, val, preSpoolVal, fullSpoolVal, prec, lowest, highest in stats:
baseBox = wx.BoxSizer(wx.VERTICAL)
baseBox.Add(wx.StaticText(contentPanel, wx.ID_ANY, label), 0, wx.ALIGN_CENTER)
if "Capacitor" in outgoingType:
lbl = wx.StaticText(parent, wx.ID_ANY, "0 GJ/s")
else:
lbl = wx.StaticText(parent, wx.ID_ANY, "0 HP/s")
baseBox.Add(wx.StaticText(contentPanel, wx.ID_ANY, labelDesc), 0, wx.ALIGN_CENTER)
lbl = wx.StaticText(parent, wx.ID_ANY, valueFormat.format(0, ""))
lbl.SetToolTip(wx.ToolTip(tooltip))
setattr(self, "label%s" % outgoingType, lbl)
setattr(self, labelName, lbl)
baseBox.Add(lbl, 0, wx.ALIGN_CENTER)
self._cachedValues.append(0)
counter += 1
sizerOutgoing.Add(baseBox, 1, wx.ALIGN_LEFT)
def refreshPanel(self, fit):
# If we did anything intresting, we'd update our labels to reflect the new fit's stats here
stats = [
("labelRemoteArmor", lambda: fit.remoteReps["Armor"], 3, 0, 0, "%s HP/s", None),
("labelRemoteShield", lambda: fit.remoteReps["Shield"], 3, 0, 0, "%s HP/s", None),
("labelRemoteHull", lambda: fit.remoteReps["Hull"], 3, 0, 0, "%s HP/s", None),
("labelRemoteCapacitor", lambda: fit.remoteReps["Capacitor"], 3, 0, 0, "%s GJ/s", None),
]
def formatTooltip(text, preSpool, fullSpool, prec, lowest, highest):
if roundToPrec(preSpool, prec) == roundToPrec(fullSpool, prec):
return False, text
else:
return True, "{}\nSpool up: {}-{}".format(
text,
formatAmount(preSpool, prec, lowest, highest),
formatAmount(fullSpool, prec, lowest, highest))
# TODO: fetch spoolup option
defaultSpoolValue = 1
counter = 0
for labelName, value, prec, lowest, highest, valueFormat, altFormat in stats:
for labelName, labelDesc, valueFormat, image, tooltip, val, preSpoolVal, fullSpoolVal, prec, lowest, highest in stats:
label = getattr(self, labelName)
value = value() if fit is not None else 0
value = value if value is not None else 0
if self._cachedValues[counter] != value:
valueStr = formatAmount(value, prec, lowest, highest)
label.SetLabel(valueFormat % valueStr)
tipStr = valueFormat % valueStr if altFormat is None else altFormat % value
label.SetToolTip(wx.ToolTip(tipStr))
self._cachedValues[counter] = value
val = val(fit, defaultSpoolValue) if fit is not None else 0
preSpoolVal = preSpoolVal(fit) if fit is not None else 0
fullSpoolVal = fullSpoolVal(fit) if fit is not None else 0
if self._cachedValues[counter] != val:
hasSpool, tooltipText = formatTooltip(tooltip, preSpoolVal, fullSpoolVal, prec, lowest, highest)
label.SetLabel(valueFormat.format(
formatAmount(val, prec, lowest, highest),
"\u02e2" if hasSpool else ""))
label.SetToolTip(wx.ToolTip(tooltipText))
self._cachedValues[counter] = val
counter += 1
self.panel.Layout()
self.headerPanel.Layout()

View File

@@ -63,15 +63,20 @@ class RechargeViewFull(StatsView):
# Add an empty label first for correct alignment.
sizerTankStats.Add(wx.StaticText(contentPanel, wx.ID_ANY, ""), 0)
toolTipText = {"shieldPassive": "Passive shield recharge", "shieldActive": "Active shield boost",
"armorActive": "Armor repair amount", "hullActive": "Hull repair amount"}
toolTipText = {
"shieldPassive": "Passive shield recharge",
"shieldActive": "Active shield boost",
"armorActive": "Armor repair amount",
"hullActive": "Hull repair amount"}
for tankType in ("shieldPassive", "shieldActive", "armorActive", "hullActive"):
bitmap = BitmapLoader.getStaticBitmap("%s_big" % tankType, contentPanel, "gui")
tooltip = wx.ToolTip(toolTipText[tankType])
bitmap.SetToolTip(tooltip)
sizerTankStats.Add(bitmap, 0, wx.ALIGN_CENTER)
toolTipText = {"reinforced": "Reinforced", "sustained": "Sustained"}
toolTipText = {
"reinforced": "Reinforced",
"sustained": "Sustained"}
for stability in ("reinforced", "sustained"):
bitmap = BitmapLoader.getStaticBitmap("regen%s_big" % stability.capitalize(), contentPanel, "gui")
tooltip = wx.ToolTip(toolTipText[stability])
@@ -85,7 +90,6 @@ class RechargeViewFull(StatsView):
tankTypeCap = tankType[0].capitalize() + tankType[1:]
lbl = wx.StaticText(contentPanel, wx.ID_ANY, "0.0", style=wx.ALIGN_RIGHT)
setattr(self, "labelTank%s%s" % (stability.capitalize(), tankTypeCap), lbl)
box = wx.BoxSizer(wx.HORIZONTAL)
box.Add(lbl, 0, wx.EXPAND)
@@ -115,9 +119,23 @@ class RechargeViewFull(StatsView):
unitlbl = getattr(self, "unitLabelTank%s%sActive" % (stability.capitalize(), name.capitalize()))
unitlbl.SetLabel(unit)
if tank is not None:
lbl.SetLabel("%.1f" % tank["%sRepair" % name])
amount = tank["{}Repair".format(name)]
else:
lbl.SetLabel("0.0")
amount = 0
if tank is not None and name == "armor":
preSpoolAmount = tank["armorRepairPreSpool"]
fullSpoolAmount = tank["armorRepairFullSpool"]
if round(preSpoolAmount, 1) != round(fullSpoolAmount, 1):
ttText = "Spool up: {:.1f}-{:.1f}".format(preSpoolAmount, fullSpoolAmount)
else:
ttText = ""
else:
ttText = ""
lbl.SetLabel("{:.1f}{}".format(amount, "\u02e2" if ttText else ""))
lbl.SetToolTip(wx.ToolTip(ttText))
unitlbl.SetToolTip(wx.ToolTip(ttText))
if fit is not None:
label = getattr(self, "labelTankSustainedShieldPassive")

View File

@@ -27,7 +27,7 @@ from gui.viewColumn import ViewColumn
from gui.bitmap_loader import BitmapLoader
from gui.utils.numberFormatter import formatAmount
from gui.utils.listFormatter import formatList
from eos.saveddata.drone import Drone
from eos.utils.spoolSupport import SpoolType, SpoolOptions
class Miscellanea(ViewColumn):
@@ -93,7 +93,9 @@ class Miscellanea(ViewColumn):
text = ""
tooltip = ""
elif max(doomsday_duration / doomsday_dottime, 1) > 1:
text = "{0} dmg over {1} s".format(formatAmount(volley * (doomsday_duration / doomsday_dottime), 3, 0, 3), doomsday_duration / 1000)
text = "{} over {}s".format(
formatAmount(volley * (doomsday_duration / doomsday_dottime), 3, 0, 6),
formatAmount((doomsday_duration / 1000), 0, 0, 0))
tooltip = "Raw damage done over time"
else:
text = "{0} dmg".format(formatAmount(volley * (doomsday_duration / doomsday_dottime), 3, 0, 3))
@@ -115,16 +117,13 @@ class Miscellanea(ViewColumn):
text = "{0}".format(formatAmount(trackingSpeed, 3, 0, 3))
tooltip = "tracking speed"
info.append((text, tooltip))
maxBonusDamage = stuff.getModifiedItemAttr("damageMultiplierBonusMax")
bonusDamagePerCycle = stuff.getModifiedItemAttr("damageMultiplierBonusPerCycle")
cycleTime = stuff.getModifiedItemAttr("speed")
if maxBonusDamage and bonusDamagePerCycle and cycleTime:
cyclesToFullDamage = int(maxBonusDamage / bonusDamagePerCycle)
timeToFullDamage = (cycleTime / 1000) * cyclesToFullDamage
if cyclesToFullDamage:
text = "{0}s".format(formatAmount(timeToFullDamage, 3, 0, 3))
tooltip = "spool-up time"
info.append((text, tooltip))
# TODO: fetch spoolup option
defaultSpoolValue = 1
spoolTime = stuff.getSpoolData(spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False))[1]
if spoolTime:
text = "{0}s".format(formatAmount(spoolTime, 3, 0, 3))
tooltip = "spool up time"
info.append((text, tooltip))
if not info:
return "", None
text = ' | '.join(i[0] for i in info)
@@ -333,39 +332,61 @@ class Miscellanea(ViewColumn):
tooltip = "Sensor recalibration time"
return text, tooltip
elif itemGroup == "Remote Armor Repairer":
repAmount = stuff.getModifiedItemAttr("armorDamageAmount")
cycleTime = stuff.getModifiedItemAttr("duration")
if not repAmount or not cycleTime:
rps = stuff.getRemoteReps(ignoreState=True)[1]
if not rps:
return "", None
repPerSec = float(repAmount) * 1000 / cycleTime
text = "{0}/s".format(formatAmount(repPerSec, 3, 0, 3, forceSign=True))
text = "{0}/s".format(formatAmount(rps, 3, 0, 3, forceSign=True))
tooltip = "Armor repaired per second"
return text, tooltip
elif itemGroup == "Remote Shield Booster":
repAmount = stuff.getModifiedItemAttr("shieldBonus")
cycleTime = stuff.cycleTime
if not repAmount or not cycleTime:
elif itemGroup == "Mutadaptive Remote Armor Repairer":
# TODO: fetch spoolup option
defaultSpoolValue = 1
spoolOptDefault = SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False)
spoolOptPre = SpoolOptions(SpoolType.SCALE, 0, True)
spoolOptFull = SpoolOptions(SpoolType.SCALE, 1, True)
rrType, rps = stuff.getRemoteReps(spoolOptions=spoolOptDefault, ignoreState=True)
rrTypePre, rpsPre = stuff.getRemoteReps(spoolOptions=spoolOptPre, ignoreState=True)
rrTypeFull, rpsFull = stuff.getRemoteReps(spoolOptions=spoolOptFull, ignoreState=True)
if not rps:
return "", None
repPerSec = float(repAmount) * 1000 / cycleTime
text = "{0}/s".format(formatAmount(repPerSec, 3, 0, 3, forceSign=True))
text = []
tooltip = []
text.append("{}/s".format(formatAmount(rps, 3, 0, 3, forceSign=True)))
tooltip.append("Armor repaired per second")
spoolTime = stuff.getSpoolData(spoolOptDefault)[1]
if spoolTime:
text.append("{}s".format(formatAmount(spoolTime, 3, 0, 3)))
tooltip.append("spool up time")
text = " | ".join(text)
tooltip = " and ".join(tooltip)
spoolTimePre = stuff.getSpoolData(spoolOptPre)[1]
spoolTimeFull = stuff.getSpoolData(spoolOptFull)[1]
if spoolTimePre != spoolTimeFull:
tooltip = "{}\nSpool up: {}-{} over {}s".format(
tooltip,
formatAmount(rpsPre, 3, 0, 3),
formatAmount(rpsFull, 3, 0, 3),
formatAmount(spoolTimeFull - spoolTimePre, 3, 0, 3))
return text, tooltip
elif itemGroup == "Remote Shield Booster":
rps = stuff.getRemoteReps(ignoreState=True)[1]
if not rps:
return "", None
text = "{0}/s".format(formatAmount(rps, 3, 0, 3, forceSign=True))
tooltip = "Shield transferred per second"
return text, tooltip
elif itemGroup == "Remote Capacitor Transmitter":
repAmount = stuff.getModifiedItemAttr("powerTransferAmount")
cycleTime = stuff.cycleTime
if not repAmount or not cycleTime:
rps = stuff.getRemoteReps(ignoreState=True)[1]
if not rps:
return "", None
repPerSec = float(repAmount) * 1000 / cycleTime
text = "{0}/s".format(formatAmount(repPerSec, 3, 0, 3, forceSign=True))
text = "{0}/s".format(formatAmount(rps, 3, 0, 3, forceSign=True))
tooltip = "Energy transferred per second"
return text, tooltip
elif itemGroup == "Remote Hull Repairer":
repAmount = stuff.getModifiedItemAttr("structureDamageAmount")
cycleTime = stuff.cycleTime
if not repAmount or not cycleTime:
rps = stuff.getRemoteReps(ignoreState=True)[1]
if not rps:
return "", None
repPerSec = float(repAmount) * 1000 / cycleTime
text = "{0}/s".format(formatAmount(repPerSec, 3, 0, 3, forceSign=True))
text = "{0}/s".format(formatAmount(rps, 3, 0, 3, forceSign=True))
tooltip = "Structure repaired per second"
return text, tooltip
elif itemGroup == "Gang Coordinator":
@@ -463,28 +484,11 @@ class Miscellanea(ViewColumn):
tooltip = "Mining Yield per second ({0} per hour)".format(formatAmount(minePerSec * 3600, 3, 0, 3))
return text, tooltip
elif itemGroup == "Logistic Drone":
armorAmount = stuff.getModifiedItemAttr("armorDamageAmount")
shieldAmount = stuff.getModifiedItemAttr("shieldBonus")
hullAmount = stuff.getModifiedItemAttr("structureDamageAmount")
repAmount = armorAmount or shieldAmount or hullAmount
cycleTime = stuff.getModifiedItemAttr("duration")
if not repAmount or not cycleTime:
repType, rps = stuff.getRemoteReps(ignoreState=True)
if not repType:
return "", None
repPerSecPerDrone = repPerSec = float(repAmount) * 1000 / cycleTime
if isinstance(stuff, Drone):
repPerSec *= stuff.amount
text = "{0}/s".format(formatAmount(repPerSec, 3, 0, 3))
ttEntries = []
if hullAmount is not None and repAmount == hullAmount:
ttEntries.append("structure")
if armorAmount is not None and repAmount == armorAmount:
ttEntries.append("armor")
if shieldAmount is not None and repAmount == shieldAmount:
ttEntries.append("shield")
tooltip = "{0} HP repaired per second\n{1} HP/s per drone".format(formatList(ttEntries).capitalize(), repPerSecPerDrone)
text = "{}/s".format(formatAmount(rps, 3, 0, 3))
tooltip = "{} HP repaired per second\n{} HP/s per drone".format(repType, formatAmount(rps / stuff.amount, 3, 0, 3))
return text, tooltip
elif itemGroup == "Energy Neutralizer Drone":
neutAmount = stuff.getModifiedItemAttr("energyNeutralizerAmount")
@@ -500,7 +504,7 @@ class Miscellanea(ViewColumn):
text = "{0}s".format(cycleTime)
tooltip = "Spoolup time"
return text, tooltip
elif itemGroup in ("Siege Module", "Cynosural Field"):
elif itemGroup in ("Siege Module", "Cynosural Field Generator"):
amt = stuff.getModifiedItemAttr("consumptionQuantity")
if amt:
typeID = stuff.getModifiedItemAttr("consumptionType")

View File

@@ -22,6 +22,7 @@ import wx
from eos.saveddata.cargo import Cargo
from eos.saveddata.drone import Drone
from eos.saveddata.price import PriceStatus
from service.price import Price as ServicePrice
from gui.viewColumn import ViewColumn
from gui.bitmap_loader import BitmapLoader
@@ -45,13 +46,16 @@ class Price(ViewColumn):
if stuff.isEmpty:
return ""
price = stuff.item.price
priceObj = stuff.item.price
if not price or not price.isValid:
if not priceObj.isValid:
return False
# Fetch actual price as float to not modify its value on Price object
price = price.price
price = priceObj.price
if price == 0:
return ""
if isinstance(stuff, Drone) or isinstance(stuff, Cargo):
price *= stuff.amount
@@ -63,10 +67,12 @@ class Price(ViewColumn):
def callback(item):
price = item[0]
text = formatAmount(price.price, 3, 3, 9, currency=True) if price.price else ""
if price.failed:
text += " (!)"
colItem.SetText(text)
textItems = []
if price.price:
textItems.append(formatAmount(price.price, 3, 3, 9, currency=True))
if price.status == PriceStatus.fail:
textItems.append("(!)")
colItem.SetText(" ".join(textItems))
display.SetItem(colItem)

View File

@@ -129,7 +129,12 @@ class CharacterEntityEditor(EntityEditor):
def DoRename(self, entity, name):
sChar = Character.getInstance()
sChar.rename(entity, name)
if entity.alphaCloneID:
trimmed_name = re.sub('[ \(\u03B1\)]+$', '', name)
sChar.rename(entity, trimmed_name)
else:
sChar.rename(entity, name)
def DoCopy(self, entity, name):
sChar = Character.getInstance()

View File

@@ -188,6 +188,7 @@ from gui.builtinContextMenus import ( # noqa: E402,F401
marketJump,
# droneSplit,
itemRemove,
fillWithModule,
droneRemoveStack,
ammoPattern,
project,

View File

@@ -18,9 +18,13 @@
# =============================================================================
from collections import OrderedDict
# noinspection PyPackageRequirements
import wx
from service.port.eft import EFT_OPTIONS
from service.port.multibuy import MULTIBUY_OPTIONS
from service.settings import SettingsProvider
@@ -37,39 +41,53 @@ class CopySelectDialog(wx.Dialog):
style=wx.DEFAULT_DIALOG_STYLE)
mainSizer = wx.BoxSizer(wx.VERTICAL)
self.settings = SettingsProvider.getInstance().getSettings("pyfaExport", {"format": 0, "options": 0})
self.copyFormats = OrderedDict((
("EFT", (CopySelectDialog.copyFormatEft, EFT_OPTIONS)),
("MultiBuy", (CopySelectDialog.copyFormatMultiBuy, MULTIBUY_OPTIONS)),
("ESI", (CopySelectDialog.copyFormatEsi, None)),
("EFS", (CopySelectDialog.copyFormatEfs, None)),
# ("XML", (CopySelectDialog.copyFormatXml, None)),
# ("DNA", (CopySelectDialog.copyFormatDna, None)),
))
self.copyFormats = {
"EFT": CopySelectDialog.copyFormatEft,
"XML": CopySelectDialog.copyFormatXml,
"DNA": CopySelectDialog.copyFormatDna,
"ESI": CopySelectDialog.copyFormatEsi,
"MultiBuy": CopySelectDialog.copyFormatMultiBuy,
"EFS": CopySelectDialog.copyFormatEfs
}
defaultFormatOptions = {}
for formatId, formatOptions in self.copyFormats.values():
if formatOptions is None:
continue
defaultFormatOptions[formatId] = {opt[0]: opt[3] for opt in formatOptions}
self.settings = SettingsProvider.getInstance().getSettings("pyfaExport", {"format": 0, "options": defaultFormatOptions})
# Options used to be stored as int (EFT export options only),
# overwrite them with new format when needed
if isinstance(self.settings["options"], int):
self.settings["options"] = defaultFormatOptions
self.options = {}
for i, format in enumerate(self.copyFormats.keys()):
if i == 0:
rdo = wx.RadioButton(self, wx.ID_ANY, format, style=wx.RB_GROUP)
initialized = False
for formatName, formatData in self.copyFormats.items():
formatId, formatOptions = formatData
if not initialized:
rdo = wx.RadioButton(self, wx.ID_ANY, formatName, style=wx.RB_GROUP)
initialized = True
else:
rdo = wx.RadioButton(self, wx.ID_ANY, format)
rdo = wx.RadioButton(self, wx.ID_ANY, formatName)
rdo.Bind(wx.EVT_RADIOBUTTON, self.Selected)
if self.settings['format'] == self.copyFormats[format]:
if self.settings['format'] == formatId:
rdo.SetValue(True)
self.copyFormat = self.copyFormats[format]
self.copyFormat = formatId
mainSizer.Add(rdo, 0, wx.EXPAND | wx.ALL, 5)
if format == "EFT":
if formatOptions:
bsizer = wx.BoxSizer(wx.VERTICAL)
self.options[formatId] = {}
for x, v in EFT_OPTIONS.items():
ch = wx.CheckBox(self, -1, v['name'])
self.options[x] = ch
if self.settings['options'] & x:
ch.SetValue(True)
bsizer.Add(ch, 1, wx.EXPAND | wx.TOP | wx.BOTTOM, 3)
for optId, optName, optDesc, _ in formatOptions:
checkbox = wx.CheckBox(self, -1, optName)
self.options[formatId][optId] = checkbox
if self.settings['options'].get(formatId, {}).get(optId, defaultFormatOptions.get(formatId, {}).get(optId)):
checkbox.SetValue(True)
bsizer.Add(checkbox, 1, wx.EXPAND | wx.TOP | wx.BOTTOM, 3)
mainSizer.Add(bsizer, 1, wx.EXPAND | wx.LEFT, 20)
buttonSizer = self.CreateButtonSizer(wx.OK | wx.CANCEL)
@@ -83,21 +101,21 @@ class CopySelectDialog(wx.Dialog):
def Selected(self, event):
obj = event.GetEventObject()
format = obj.GetLabel()
self.copyFormat = self.copyFormats[format]
formatName = obj.GetLabel()
self.copyFormat = self.copyFormats[formatName][0]
self.toggleOptions()
self.Fit()
def toggleOptions(self):
for ch in self.options.values():
ch.Enable(self.GetSelected() == CopySelectDialog.copyFormatEft)
for formatId in self.options:
for checkbox in self.options[formatId].values():
checkbox.Enable(self.GetSelected() == formatId)
def GetSelected(self):
return self.copyFormat
def GetOptions(self):
i = 0
for x, v in self.options.items():
if v.IsChecked():
i = i ^ x
return i
options = {}
for formatId in self.options:
options[formatId] = {optId: ch.IsChecked() for optId, ch in self.options[formatId].items()}
return options

View File

@@ -67,7 +67,7 @@ class ErrorFrame(wx.Frame):
from eos.config import gamedata_version, gamedata_date
time = datetime.datetime.fromtimestamp(int(gamedata_date)).strftime('%Y-%m-%d %H:%M:%S')
version = "pyfa v" + config.getVersion() + '\nEVE Data Version: {} ({})\n\n'.format(gamedata_version, time) # gui.aboutData.versionString
version = "pyfa " + config.getVersion() + '\nEVE Data Version: {} ({})\n\n'.format(gamedata_version, time) # gui.aboutData.versionString
desc = "pyfa has experienced an unexpected issue. Below is a message that contains crucial\n" \
"information about how this was triggered. Please contact the developers with the\n" \

View File

@@ -2,6 +2,7 @@ from .guiToggleModuleState import GuiModuleStateChangeCommand
from .guiAddModule import GuiModuleAddCommand
from .guiRemoveModule import GuiModuleRemoveCommand
from .guiAddCharge import GuiModuleAddChargeCommand
from .guiFillWithModule import GuiFillWithModuleCommand
from .guiSwapCloneModule import GuiModuleSwapOrCloneCommand
from .guiRemoveCargo import GuiRemoveCargoCommand
from .guiAddCargo import GuiAddCargoCommand

View File

@@ -0,0 +1,50 @@
import wx
import gui.mainFrame
from gui import globalEvents as GE
from .calc.fitAddModule import FitAddModuleCommand
from service.fit import Fit
from logbook import Logger
pyfalog = Logger(__name__)
class GuiFillWithModuleCommand(wx.Command):
def __init__(self, fitID, itemID, position=None):
"""
Handles adding an item, usually a module, to the Fitting Window.
:param fitID: The fit ID that we are modifying
:param itemID: The item that is to be added to the Fitting View. If this turns out to be a charge, we attempt to
set the charge on the underlying module (requires position)
:param position: Optional. The position in fit.modules that we are attempting to set the item to
"""
wx.Command.__init__(self, True, "Module Add: {}".format(itemID))
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.sFit = Fit.getInstance()
self.fitID = fitID
self.itemID = itemID
self.internal_history = wx.CommandProcessor()
self.position = position
self.old_mod = None
def Do(self):
pyfalog.debug("{} Do()".format(self))
pyfalog.debug("Trying to append a module")
added_modules = 0
success = self.internal_history.Submit(FitAddModuleCommand(self.fitID, self.itemID))
while (success):
added_modules += 1
success = self.internal_history.Submit(FitAddModuleCommand(self.fitID, self.itemID))
if added_modules > 0:
self.sFit.recalc(self.fitID)
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.fitID, action="modadd", typeID=self.itemID))
return True
return False
def Undo(self):
pyfalog.debug("{} Undo()".format(self))
for _ in self.internal_history.Commands:
self.internal_history.Undo()
self.sFit.recalc(self.fitID)
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.fitID, action="moddel", typeID=self.itemID))
return True

View File

@@ -91,7 +91,7 @@ class ItemStatsDialog(wx.Dialog):
self.SetMinSize((300, 200))
if "wxGTK" in wx.PlatformInfo: # GTK has huge tab widgets, give it a bit more room
self.SetSize((580, 500))
self.SetSize((640, 620))
else:
self.SetSize((550, 500))
# self.SetMaxSize((500, -1))
@@ -168,8 +168,9 @@ class ItemStatsContainer(wx.Panel):
self.mutator = ItemMutator(self.nbContainer, stuff, item)
self.nbContainer.AddPage(self.mutator, "Mutations")
self.desc = ItemDescription(self.nbContainer, stuff, item)
self.nbContainer.AddPage(self.desc, "Description")
if item.description:
self.desc = ItemDescription(self.nbContainer, stuff, item)
self.nbContainer.AddPage(self.desc, "Description")
self.params = ItemParams(self.nbContainer, stuff, item, context)
self.nbContainer.AddPage(self.params, "Attributes")

View File

@@ -703,10 +703,6 @@ class MainFrame(wx.Frame):
fit = db_getFit(self.getActiveFit())
toClipboard(Port.exportEft(fit, options))
def clipboardEftImps(self, options):
fit = db_getFit(self.getActiveFit())
toClipboard(Port.exportEftImps(fit))
def clipboardDna(self, options):
fit = db_getFit(self.getActiveFit())
toClipboard(Port.exportDna(fit))
@@ -721,7 +717,7 @@ class MainFrame(wx.Frame):
def clipboardMultiBuy(self, options):
fit = db_getFit(self.getActiveFit())
toClipboard(Port.exportMultiBuy(fit))
toClipboard(Port.exportMultiBuy(fit, options))
def clipboardEfs(self, options):
fit = db_getFit(self.getActiveFit())
@@ -744,22 +740,22 @@ class MainFrame(wx.Frame):
def exportToClipboard(self, event):
CopySelectDict = {CopySelectDialog.copyFormatEft: self.clipboardEft,
# CopySelectDialog.copyFormatEftImps: self.clipboardEftImps,
CopySelectDialog.copyFormatXml: self.clipboardXml,
CopySelectDialog.copyFormatDna: self.clipboardDna,
CopySelectDialog.copyFormatEsi: self.clipboardEsi,
CopySelectDialog.copyFormatMultiBuy: self.clipboardMultiBuy,
CopySelectDialog.copyFormatEfs: self.clipboardEfs}
dlg = CopySelectDialog(self)
dlg.ShowModal()
selected = dlg.GetSelected()
options = dlg.GetOptions()
btnPressed = dlg.ShowModal()
settings = SettingsProvider.getInstance().getSettings("pyfaExport")
settings["format"] = selected
settings["options"] = options
if btnPressed == wx.ID_OK:
selected = dlg.GetSelected()
options = dlg.GetOptions()
CopySelectDict[selected](options)
settings = SettingsProvider.getInstance().getSettings("pyfaExport")
settings["format"] = selected
settings["options"] = options
CopySelectDict[selected](options.get(selected))
try:
dlg.Destroy()

View File

@@ -6,3 +6,23 @@ def YesNoDialog(question='Are you sure you want to do this?', caption='Yes or no
result = dlg.ShowModal() == wx.ID_YES
dlg.Destroy()
return result
def HandleCtrlBackspace(textControl):
"""
Handles the behavior of Windows ctrl+space
deletes everything from the cursor to the left,
up to the next whitespace.
"""
curPos = textControl.GetInsertionPoint()
searchText = textControl.GetValue()
foundChar = False
for startIndex in range(curPos, -1, -1):
if startIndex - 1 < 0:
break
if searchText[startIndex - 1] != " ":
foundChar = True
elif foundChar:
break
textControl.Remove(startIndex, curPos)
textControl.SetInsertionPoint(startIndex)