Merge branch 'pyfa-org:master' into master

This commit is contained in:
StormDelay
2025-05-20 18:33:48 +02:00
committed by GitHub
791 changed files with 382527 additions and 28685 deletions

View File

@@ -212,7 +212,7 @@ class AttributeGauge(wx.Window):
for x in range(1, 20):
dc.SetBrush(wx.Brush(wx.LIGHT_GREY))
dc.SetPen(wx.Pen(wx.LIGHT_GREY))
dc.DrawRectangle(x * 10, 1, 1, rect.height)
dc.DrawRectangle(round(x * 10), 1, 1, round(rect.height))
dc.SetBrush(wx.Brush(colour))
dc.SetPen(wx.Pen(colour))
@@ -222,19 +222,19 @@ class AttributeGauge(wx.Window):
if value >= 0:
padding = (half if is_even else math.ceil(half - 1)) + 1
dc.DrawRectangle(padding, 1, w, rect.height)
dc.DrawRectangle(round(padding), 1, round(w), round(rect.height))
else:
padding = half - w + 1 if is_even else math.ceil(half) - (w - 1)
dc.DrawRectangle(padding, 1, w, rect.height)
dc.DrawRectangle(round(padding), 1, round(w), round(rect.height))
if self.leading_edge and (self.edge_on_neutral or value != 0):
dc.SetPen(wx.Pen(wx.WHITE))
dc.SetBrush(wx.Brush(wx.WHITE))
if value > 0:
dc.DrawRectangle(min(padding + w, rect.width), 1, 1, rect.height)
dc.DrawRectangle(round(min(padding + w, rect.width)), 1, 1, round(rect.height))
else:
dc.DrawRectangle(max(padding - 1, 1), 1, 1, rect.height)
dc.DrawRectangle(round(max(padding - 1, 1)), 1, 1, round(rect.height))
def OnTimer(self, event):
old_value = self._old_percentage

View File

@@ -103,10 +103,9 @@ class BitmapLoader:
pyfalog.warning("Missing icon file: {0}/{1}".format(location, filename))
return None
bmp: wx.Bitmap = img.ConvertToBitmap()
if scale > 1:
bmp.SetSize((bmp.GetWidth() // scale, bmp.GetHeight() // scale))
return bmp
return img.Scale(round(img.GetWidth() // scale), round(img.GetHeight() // scale)).ConvertToBitmap()
return img.ConvertToBitmap()
@classmethod
def loadScaledBitmap(cls, name, location, scale=0):

View File

@@ -43,6 +43,10 @@ class BoosterViewDrop(wx.DropTarget):
if self.GetData():
dragged_data = DragDropHelper.data
data = dragged_data.split(':')
if dragged_data is None:
return t
self.dropFn(x, y, data)
return t

View File

@@ -41,6 +41,10 @@ class CargoViewDrop(wx.DropTarget):
def OnData(self, x, y, t):
if self.GetData():
dragged_data = DragDropHelper.data
if dragged_data is None:
return t
data = dragged_data.split(':')
self.dropFn(x, y, data)
return t

View File

@@ -56,6 +56,10 @@ class CommandViewDrop(wx.DropTarget):
def OnData(self, x, y, t):
if self.GetData():
dragged_data = DragDropHelper.data
if dragged_data is None:
return t
data = dragged_data.split(':')
self.dropFn(x, y, data)
return t

View File

@@ -52,6 +52,10 @@ class DroneViewDrop(wx.DropTarget):
def OnData(self, x, y, t):
if self.GetData():
dragged_data = DragDropHelper.data
if dragged_data is None:
return t
data = dragged_data.split(':')
self.dropFn(x, y, data)
return t
@@ -195,7 +199,11 @@ class DroneView(Display):
@staticmethod
def droneKey(drone):
groupName = Market.getInstance().getMarketGroupByItem(drone.item).marketGroupName
if drone.isMutated:
item = drone.baseItem
else:
item = drone.item
groupName = Market.getInstance().getMarketGroupByItem(item).marketGroupName
return (DRONE_ORDER.index(groupName), drone.isMutated, drone.fullName)
def fitChanged(self, event):

View File

@@ -34,7 +34,10 @@ from service.fit import Fit
from service.market import Market
FIGHTER_ORDER = ('Light Fighter', 'Heavy Fighter', 'Support Fighter')
FIGHTER_ORDER = (
'Light Fighter', 'Structure Light Fighter',
'Heavy Fighter', 'Structure Heavy Fighter',
'Support Fighter', 'Structure Support Fighter')
_t = wx.GetTranslation
@@ -49,6 +52,10 @@ class FighterViewDrop(wx.DropTarget):
def OnData(self, x, y, t):
if self.GetData():
dragged_data = DragDropHelper.data
if dragged_data is None:
return t
data = dragged_data.split(':')
self.dropFn(x, y, data)
return t

View File

@@ -46,6 +46,10 @@ class ImplantViewDrop(wx.DropTarget):
def OnData(self, x, y, t):
if self.GetData():
dragged_data = DragDropHelper.data
if dragged_data is None:
return t
data = dragged_data.split(':')
self.dropFn(x, y, data)
return t

View File

@@ -65,6 +65,10 @@ class ProjectedViewDrop(wx.DropTarget):
def OnData(self, x, y, t):
if self.GetData():
dragged_data = DragDropHelper.data
if dragged_data is None:
return t
data = dragged_data.split(':')
self.dropFn(x, y, data)
return t

View File

@@ -20,6 +20,7 @@ from gui.builtinContextMenus.targetProfile import editor
from gui.builtinContextMenus import itemStats
from gui.builtinContextMenus import itemMarketJump
from gui.builtinContextMenus import fitSystemSecurity # Not really an item info but want to keep it here
from gui.builtinContextMenus import fitPilotSecurity # Not really an item info but want to keep it here
from gui.builtinContextMenus import shipJump
# Generic item manipulations
from gui.builtinContextMenus import itemRemove
@@ -35,6 +36,7 @@ from gui.builtinContextMenus import skillAffectors
from gui.builtinContextMenus import itemFill
from gui.builtinContextMenus import droneAddStack
from gui.builtinContextMenus import cargoAdd
from gui.builtinContextMenus import cargoFill
from gui.builtinContextMenus import cargoAddAmmo
from gui.builtinContextMenus import itemProject
from gui.builtinContextMenus import ammoToDmgPattern

View File

@@ -25,7 +25,7 @@ class AddToCargoAmmo(ContextMenuSingle):
return True
def getText(self, callingWindow, itmContext, mainItem):
if mainItem.marketGroup.name == "Scan Probes":
if mainItem.marketGroup and mainItem.marketGroup.name == "Scan Probes":
return _t("Add {0} to Cargo (x8)").format(itmContext)
return _t("Add {0} to Cargo (x1000)").format(itmContext)
@@ -34,7 +34,7 @@ class AddToCargoAmmo(ContextMenuSingle):
fitID = self.mainFrame.getActiveFit()
typeID = int(mainItem.ID)
if mainItem.marketGroup.name == "Scan Probes":
if mainItem.marketGroup and mainItem.marketGroup.name == "Scan Probes":
command = cmd.GuiAddCargoCommand(fitID=fitID, itemID=typeID, amount=8)
else:
command = cmd.GuiAddCargoCommand(fitID=fitID, itemID=typeID, amount=1000)

View File

@@ -0,0 +1,68 @@
import wx
import gui.fitCommands as cmd
import gui.mainFrame
from gui.contextMenu import ContextMenuSingle
from service.fit import Fit
from eos.saveddata.cargo import Cargo
_t = wx.GetTranslation
class FillCargoWithItem(ContextMenuSingle):
def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
def display(self, callingWindow, srcContext, mainItem):
if srcContext not in ("marketItemGroup", "marketItemMisc", "cargoItem"):
return False
if mainItem is None:
return False
if self.mainFrame.getActiveFit() is None:
return False
if srcContext in ("marketItemGroup", "marketItemMisc"):
if not (mainItem.isCharge or mainItem.isCommodity):
return False
return True
def getText(self, callingWindow, itmContext, mainItem):
return _t("Fill Cargo With {0}").format(itmContext)
def activate(self, callingWindow, fullContext, mainItem, i):
fitID = self.mainFrame.getActiveFit()
fit = Fit.getInstance().getFit(fitID)
if isinstance(mainItem, Cargo):
itemVolume = mainItem.item.attributes['volume'].value
itemID = mainItem.itemID
else:
itemVolume = mainItem.attributes['volume'].value
itemID = int(mainItem.ID)
if itemVolume is None or itemVolume <= 0:
return
# Calculate how many items can fit in the cargo
cargoCapacity = fit.ship.getModifiedItemAttr("capacity")
currentCargoVolume = fit.cargoBayUsed
availableVolume = cargoCapacity - currentCargoVolume
if availableVolume <= 0:
return
# Calculate maximum amount that can fit
maxAmount = int(availableVolume / itemVolume)
if maxAmount <= 0:
return
# Add the items to cargo
command = cmd.GuiAddCargoCommand(fitID=fitID, itemID=itemID, amount=maxAmount)
if self.mainFrame.command.Submit(command):
self.mainFrame.additionsPane.select("Cargo", focus=False)
FillCargoWithItem.register()

View File

@@ -65,7 +65,6 @@ class DroneStackSplit(wx.Dialog):
self.input = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_PROCESS_ENTER)
self.input.SetValue(str(value))
self.input.SelectAll()
bSizer1.Add(self.input, 0, wx.LEFT | wx.RIGHT | wx.EXPAND, 15)
@@ -75,12 +74,13 @@ class DroneStackSplit(wx.Dialog):
bSizer3.Add(self.CreateStdDialogButtonSizer(wx.OK | wx.CANCEL), 0, wx.EXPAND)
bSizer1.Add(bSizer3, 0, wx.ALL | wx.EXPAND, 10)
self.input.SetFocus()
self.input.Bind(wx.EVT_CHAR, self.onChar)
self.input.Bind(wx.EVT_TEXT_ENTER, self.processEnter)
self.SetSizer(bSizer1)
self.CenterOnParent()
self.Fit()
self.CenterOnParent()
self.input.SetFocus()
self.input.SelectAll()
def processEnter(self, evt):
self.EndModal(wx.ID_OK)

View File

@@ -123,7 +123,11 @@ class AddEnvironmentEffect(ContextMenuUnconditional):
data.groups[_t('Abyssal Weather')] = self.getAbyssalWeather()
data.groups[_t('Sansha Incursion')] = self.getEffectBeacons(
_t('ContextMenu|ProjectedEffectManipulation|Sansha Incursion'))
data.groups[_t('Drifter Incursion')] = self.getDrifterIncursion()
data.groups[_t('Triglavian Invasion')] = self.getInvasionBeacons()
data.groups[_t('Pirate Insurgency')] = self.getEffectBeacons(
_t('ContextMenu|ProjectedEffectManipulation|Insurgency'),
extra_garbage=(_t('ContextMenu|ProjectedEffectManipulation|Beacon'),))
return data
def getEffectBeacons(self, *groups, extra_garbage=()):
@@ -174,7 +178,6 @@ class AddEnvironmentEffect(ContextMenuUnconditional):
container.append(Entry(beacon.ID, beaconname, shortname))
# Break loop on 1st result
break
data.sort()
return data
def getAbyssalWeather(self):
@@ -231,12 +234,31 @@ class AddEnvironmentEffect(ContextMenuUnconditional):
data.sort()
return data
def getDrifterIncursion(self):
data = self.getEffectBeacons(_t('ContextMenu|ProjectedEffectManipulation|Drifter Incursion'))
# Drifter Crisis
item = Market.getInstance().getItem(87294)
data.items.append(Entry(item.ID, item.name, item.name))
return data
def getInvasionBeacons(self):
data = self.getDestructibleBeacons()
# Trig Minor Victory
item = Market.getInstance().getItem(87177)
data.items.append(Entry(item.ID, item.name, item.name))
# Trig Final Liminality
item = Market.getInstance().getItem(87164)
data.items.append(Entry(item.ID, item.name, item.name))
# Turnur weather
item = Market.getInstance().getItem(74002)
data.items.append(Entry(item.ID, item.name, item.name))
return data
def getInsurgencyBeacons(self):
data = self.getDestructibleBeacons()
# Suppression Interdiction Range Beacon
item = Market.getInstance().getItem(79839)
data.items.append(Entry(item.ID, item.name, item.name))
return data
AddEnvironmentEffect.register()

View File

@@ -0,0 +1,157 @@
import re
import wx
import gui.fitCommands as cmd
import gui.mainFrame
from gui.contextMenu import ContextMenuUnconditional
from service.fit import Fit
_t = wx.GetTranslation
class FitPilotSecurityMenu(ContextMenuUnconditional):
def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
def display(self, callingWindow, srcContext):
if srcContext != "fittingShip":
return False
fitID = self.mainFrame.getActiveFit()
fit = Fit.getInstance().getFit(fitID)
if fit.ship.name not in ('Pacifier', 'Enforcer', 'Marshal', 'Sidewinder', 'Cobra', 'Python'):
return
return True
def getText(self, callingWindow, itmContext):
return _t("Pilot Security Status")
def addOption(self, menu, optionLabel, optionValue):
id = ContextMenuUnconditional.nextID()
self.optionIds[id] = optionValue
menuItem = wx.MenuItem(menu, id, optionLabel, kind=wx.ITEM_CHECK)
menu.Bind(wx.EVT_MENU, self.handleMode, menuItem)
return menuItem
def addOptionCustom(self, menu, optionLabel):
id = ContextMenuUnconditional.nextID()
menuItem = wx.MenuItem(menu, id, optionLabel, kind=wx.ITEM_CHECK)
menu.Bind(wx.EVT_MENU, self.handleModeCustom, menuItem)
return menuItem
def getSubMenu(self, callingWindow, context, rootMenu, i, pitem):
fitID = self.mainFrame.getActiveFit()
fit = Fit.getInstance().getFit(fitID)
msw = True if "wxMSW" in wx.PlatformInfo else False
self.optionIds = {}
sub = wx.Menu()
presets = (-10, -8, -6, -4, -2, 0, 1, 2, 3, 4, 5)
# Inherit
char_sec_status = round(fit.character.secStatus, 2)
menuItem = self.addOption(rootMenu if msw else sub, _t('Character') + f' ({char_sec_status})', None)
sub.Append(menuItem)
menuItem.Check(fit.pilotSecurity is None)
# Custom
label = _t('Custom')
is_checked = False
if fit.pilotSecurity is not None and fit.pilotSecurity not in presets:
sec_status = round(fit.getPilotSecurity(), 2)
label += f' ({sec_status})'
is_checked = True
menuItem = self.addOptionCustom(rootMenu if msw else sub, label)
sub.Append(menuItem)
menuItem.Check(is_checked)
sub.AppendSeparator()
# Predefined options
for sec_status in presets:
menuItem = self.addOption(rootMenu if msw else sub, str(sec_status), sec_status)
sub.Append(menuItem)
menuItem.Check(fit.pilotSecurity == sec_status)
return sub
def handleMode(self, event):
optionValue = self.optionIds[event.Id]
self.mainFrame.command.Submit(cmd.GuiChangeFitPilotSecurityCommand(
fitID=self.mainFrame.getActiveFit(),
secStatus=optionValue))
def handleModeCustom(self, event):
fitID = self.mainFrame.getActiveFit()
fit = Fit.getInstance().getFit(fitID)
sec_status = fit.getPilotSecurity()
with SecStatusChanger(self.mainFrame, value=sec_status) as dlg:
if dlg.ShowModal() == wx.ID_OK:
cleanInput = re.sub(r'[^0-9.\-+]', '', dlg.input.GetLineText(0).strip())
if cleanInput:
try:
cleanInputFloat = float(cleanInput)
except ValueError:
return
else:
return
self.mainFrame.command.Submit(cmd.GuiChangeFitPilotSecurityCommand(
fitID=fitID, secStatus=max(-10.0, min(5.0, cleanInputFloat))))
FitPilotSecurityMenu.register()
class SecStatusChanger(wx.Dialog):
def __init__(self, parent, value):
super().__init__(parent, title=_t('Change Security Status'), style=wx.DEFAULT_DIALOG_STYLE)
self.SetMinSize((346, 156))
bSizer1 = wx.BoxSizer(wx.VERTICAL)
bSizer2 = wx.BoxSizer(wx.VERTICAL)
text = wx.StaticText(self, wx.ID_ANY, _t('Security Status (min -10.0, max 5.0):'))
bSizer2.Add(text, 0)
bSizer1.Add(bSizer2, 0, wx.ALL, 10)
self.input = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_PROCESS_ENTER)
if value is None:
value = '0.0'
else:
if value == int(value):
value = int(value)
value = str(value)
self.input.SetValue(value)
bSizer1.Add(self.input, 0, wx.LEFT | wx.RIGHT | wx.EXPAND, 15)
bSizer3 = wx.BoxSizer(wx.VERTICAL)
bSizer3.Add(wx.StaticLine(self, wx.ID_ANY), 0, wx.BOTTOM | wx.EXPAND, 15)
bSizer3.Add(self.CreateStdDialogButtonSizer(wx.OK | wx.CANCEL), 0, wx.EXPAND)
bSizer1.Add(bSizer3, 0, wx.ALL | wx.EXPAND, 10)
self.input.Bind(wx.EVT_CHAR, self.onChar)
self.input.Bind(wx.EVT_TEXT_ENTER, self.processEnter)
self.SetSizer(bSizer1)
self.Fit()
self.CenterOnParent()
self.input.SetFocus()
self.input.SelectAll()
def processEnter(self, evt):
self.EndModal(wx.ID_OK)
# checks to make sure it's valid number
@staticmethod
def onChar(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

@@ -59,7 +59,6 @@ class NameDialog(wx.Dialog):
else:
value = str(value)
self.input.SetValue(value)
self.input.SelectAll()
bSizer1.Add(self.input, 0, wx.LEFT | wx.RIGHT | wx.EXPAND, 15)
@@ -69,11 +68,12 @@ class NameDialog(wx.Dialog):
bSizer3.Add(self.CreateStdDialogButtonSizer(wx.OK | wx.CANCEL), 0, wx.EXPAND)
bSizer1.Add(bSizer3, 0, wx.ALL | wx.EXPAND, 10)
self.input.SetFocus()
self.input.Bind(wx.EVT_TEXT_ENTER, self.processEnter)
self.SetSizer(bSizer1)
self.CenterOnParent()
self.Fit()
self.CenterOnParent()
self.input.SetFocus()
self.input.SelectAll()
def processEnter(self, evt):
self.EndModal(wx.ID_OK)

View File

@@ -108,7 +108,6 @@ class AmountChanger(wx.Dialog):
self.input = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_PROCESS_ENTER)
self.input.SetValue(str(value))
self.input.SelectAll()
bSizer1.Add(self.input, 0, wx.LEFT | wx.RIGHT | wx.EXPAND, 15)
@@ -118,12 +117,13 @@ class AmountChanger(wx.Dialog):
bSizer3.Add(self.CreateStdDialogButtonSizer(wx.OK | wx.CANCEL), 0, wx.EXPAND)
bSizer1.Add(bSizer3, 0, wx.ALL | wx.EXPAND, 10)
self.input.SetFocus()
self.input.Bind(wx.EVT_CHAR, self.onChar)
self.input.Bind(wx.EVT_TEXT_ENTER, self.processEnter)
self.SetSizer(bSizer1)
self.CenterOnParent()
self.Fit()
self.CenterOnParent()
self.input.SetFocus()
self.input.SelectAll()
def processEnter(self, evt):
self.EndModal(wx.ID_OK)

View File

@@ -13,6 +13,16 @@ from service.fit import Fit
_t = wx.GetTranslation
GLORIFIED_PREFIX = 'Gl. '
def nameSorter(mutaplasmid):
name = mutaplasmid.shortName
if name.startswith(GLORIFIED_PREFIX):
return name[len(GLORIFIED_PREFIX):], True
return name, False
class ChangeItemMutation(ContextMenuSingle):
def __init__(self):
@@ -45,7 +55,7 @@ class ChangeItemMutation(ContextMenuSingle):
menu = rootMenu if msw else sub
for mutaplasmid in mainItem.item.mutaplasmids:
for mutaplasmid in sorted(mainItem.item.mutaplasmids, key=nameSorter):
id = ContextMenuSingle.nextID()
self.eventIDs[id] = (mutaplasmid, mainItem)
mItem = wx.MenuItem(menu, id, mutaplasmid.shortName)

View File

@@ -94,7 +94,6 @@ class RangeChanger(wx.Dialog):
value = int(value)
value = str(value)
self.input.SetValue(value)
self.input.SelectAll()
bSizer1.Add(self.input, 0, wx.LEFT | wx.RIGHT | wx.EXPAND, 15)
@@ -104,12 +103,13 @@ class RangeChanger(wx.Dialog):
bSizer3.Add(self.CreateStdDialogButtonSizer(wx.OK | wx.CANCEL), 0, wx.EXPAND)
bSizer1.Add(bSizer3, 0, wx.ALL | wx.EXPAND, 10)
self.input.SetFocus()
self.input.Bind(wx.EVT_CHAR, self.onChar)
self.input.Bind(wx.EVT_TEXT_ENTER, self.processEnter)
self.SetSizer(bSizer1)
self.CenterOnParent()
self.Fit()
self.CenterOnParent()
self.input.SetFocus()
self.input.SelectAll()
def processEnter(self, evt):
self.EndModal(wx.ID_OK)

View File

@@ -72,6 +72,7 @@ AttrGroupDict = {
"specialAmmoHoldCapacity",
"specialCommandCenterHoldCapacity",
"specialPlanetaryCommoditiesHoldCapacity",
"specialColonyResourcesHoldCapacity",
"structureDamageLimit",
"specialSubsystemHoldCapacity",
"emDamageResonance",

View File

@@ -4,13 +4,18 @@ import wx
# noinspection PyPackageRequirements
import wx.lib.mixins.listctrl as listmix
from gui.utils.dark import isDark
class AutoListCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin, listmix.ListRowHighlighter):
def __init__(self, parent, ID, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0):
wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
listmix.ListCtrlAutoWidthMixin.__init__(self)
listmix.ListRowHighlighter.__init__(self)
if isDark():
listcol = wx.SystemSettings.GetColour(wx.SYS_COLOUR_LISTBOX)
highlight = listcol.ChangeLightness(110)
listmix.ListRowHighlighter.SetHighlightColor(self, highlight)
class AutoListCtrlNoHighlight(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin, listmix.ListRowHighlighter):
def __init__(self, parent, ID, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0):

View File

@@ -22,9 +22,9 @@ class ItemDescription(wx.Panel):
desc = item.description.replace("\n", "<br>")
# Strip font tags
desc = re.sub("<( *)font( *)color( *)=(.*?)>(?P<inside>.*?)<( *)/( *)font( *)>", "\g<inside>", desc)
desc = re.sub("<( *)font( *)color( *)=(.*?)>(?P<inside>.*?)<( *)/( *)font( *)>", r"\g<inside>", desc)
# Strip URLs
desc = re.sub("<( *)a(.*?)>(?P<inside>.*?)<( *)/( *)a( *)>", "\g<inside>", desc)
desc = re.sub("<( *)a(.*?)>(?P<inside>.*?)<( *)/( *)a( *)>", r"\g<inside>", desc)
desc = "<body bgcolor='{}' text='{}'>{}</body>".format(
bgcolor.GetAsString(wx.C2S_HTML_SYNTAX),
fgcolor.GetAsString(wx.C2S_HTML_SYNTAX),

View File

@@ -5,3 +5,5 @@ import wx.lib.newevent
ItemSelected, ITEM_SELECTED = wx.lib.newevent.NewEvent()
RECENTLY_USED_MODULES = -2
CHARGES_FOR_FIT = -3

View File

@@ -2,14 +2,17 @@ import wx
from logbook import Logger
import gui.builtinMarketBrowser.pfSearchBox as SBox
from config import slotColourMap
import gui.globalEvents as GE
from config import slotColourMap, slotColourMapDark
from eos.saveddata.module import Module
from gui.builtinMarketBrowser.events import ItemSelected, RECENTLY_USED_MODULES
from gui.builtinMarketBrowser.events import ItemSelected, RECENTLY_USED_MODULES, CHARGES_FOR_FIT
from gui.contextMenu import ContextMenu
from gui.display import Display
from gui.utils.staticHelpers import DragDropHelper
from gui.utils.dark import isDark
from service.fit import Fit
from service.market import Market
from service.ammo import Ammo
pyfalog = Logger(__name__)
@@ -31,6 +34,7 @@ class ItemView(Display):
self.filteredStore = set()
self.sMkt = marketBrowser.sMkt
self.sFit = Fit.getInstance()
self.sAmmo = Ammo.getInstance()
self.marketBrowser = marketBrowser
self.marketView = marketBrowser.marketView
@@ -50,6 +54,9 @@ class ItemView(Display):
self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.itemActivated)
self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.startDrag)
# the "charges for active fitting" needs to listen to fitting changes
self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged)
self.active = []
def delaySearch(self, evt):
@@ -90,7 +97,11 @@ class ItemView(Display):
if sel.IsOk():
# Get data field of the selected item (which is a marketGroup ID if anything was selected)
seldata = self.marketView.GetItemData(sel)
if seldata is not None and seldata != RECENTLY_USED_MODULES:
if seldata == RECENTLY_USED_MODULES:
items = self.sMkt.getRecentlyUsed()
elif seldata == CHARGES_FOR_FIT:
items = self.getChargesForActiveFit()
elif seldata is not None:
# If market group treeview item doesn't have children (other market groups or dummies),
# then it should have items in it and we want to request them
if self.marketView.ItemHasChildren(sel) is False:
@@ -102,11 +113,7 @@ class ItemView(Display):
else:
items = set()
else:
# If method was called but selection wasn't actually made or we have a hit on recently used modules
if seldata == RECENTLY_USED_MODULES:
items = self.sMkt.getRecentlyUsed()
else:
items = set()
items = set()
# Fill store
self.updateItemStore(items)
@@ -114,6 +121,9 @@ class ItemView(Display):
# Set toggle buttons / use search mode flag if recently used modules category is selected (in order to have all modules listed and not filtered)
if seldata == RECENTLY_USED_MODULES:
self.marketBrowser.mode = 'recent'
if seldata == CHARGES_FOR_FIT:
self.marketBrowser.mode = 'charges'
self.setToggles()
if context == 'tree' and self.marketBrowser.settings.get('marketMGMarketSelectMode') == 1:
@@ -122,6 +132,41 @@ class ItemView(Display):
btn.setUserSelection(True)
self.filterItemStore()
def getChargesForActiveFit(self):
fitId = self.mainFrame.getActiveFit()
# no active fit => no charges
if fitId is None:
return set()
fit = self.sFit.getFit(fitId)
# use a set so we only add one entry for each charge
items = set()
for mod in fit.modules:
charges = self.sAmmo.getModuleFlatAmmo(mod)
for charge in charges:
items.add(charge)
return items
def fitChanged(self, event):
# skip the event so the other handlers also get called
event.Skip()
if self.marketBrowser.mode != 'charges':
return
activeFitID = self.mainFrame.getActiveFit()
# if it was not the active fitting that was changed, do not do anything
if activeFitID is not None and activeFitID not in event.fitIDs:
return
items = self.getChargesForActiveFit()
# update the UI
self.updateItemStore(items)
self.filterItemStore()
def updateItemStore(self, items):
self.unfilteredStore = items
@@ -243,6 +288,7 @@ class ItemView(Display):
def columnBackground(self, colItem, item):
if self.sFit.serviceFittingOptions["colorFitBySlot"]:
return slotColourMap.get(Module.calculateSlot(item)) or self.GetBackgroundColour()
colorMap = slotColourMapDark if isDark() else slotColourMap
return colorMap.get(Module.calculateSlot(item)) or self.GetBackgroundColour()
else:
return self.GetBackgroundColour()

View File

@@ -1,7 +1,7 @@
import wx
from gui.cachingImageList import CachingImageList
from gui.builtinMarketBrowser.events import RECENTLY_USED_MODULES
from gui.builtinMarketBrowser.events import RECENTLY_USED_MODULES, CHARGES_FOR_FIT
from logbook import Logger
@@ -35,6 +35,9 @@ class MarketTree(wx.TreeCtrl):
# Add recently used modules node
rumIconId = self.addImage("market_small", "gui")
self.AppendItem(self.root, _t("Recently Used Items"), rumIconId, data=RECENTLY_USED_MODULES)
# Add charges for active fitting node
cffIconId = self.addImage("damagePattern_small", "gui")
self.AppendItem(self.root, _t("Charges For Active Fit"), cffIconId, data=CHARGES_FOR_FIT)
# Bind our lookup method to when the tree gets expanded
self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.expandLookup)

View File

@@ -253,8 +253,8 @@ class PFSearchBox(wx.Window):
else:
spad = 0
dc.DrawBitmap(self.searchBitmapShadow, self.searchButtonX + 1, self.searchButtonY + 1)
dc.DrawBitmap(self.searchBitmap, self.searchButtonX + spad, self.searchButtonY + spad)
dc.DrawBitmap(self.searchBitmapShadow, round(self.searchButtonX + 1), round(self.searchButtonY + 1))
dc.DrawBitmap(self.searchBitmap, round(self.searchButtonX + spad), round(self.searchButtonY + spad))
if self.isCancelButtonVisible:
if self.cancelBitmap:
@@ -262,8 +262,8 @@ class PFSearchBox(wx.Window):
cpad = 1
else:
cpad = 0
dc.DrawBitmap(self.cancelBitmapShadow, self.cancelButtonX + 1, self.cancelButtonY + 1)
dc.DrawBitmap(self.cancelBitmap, self.cancelButtonX + cpad, self.cancelButtonY + cpad)
dc.DrawBitmap(self.cancelBitmapShadow, round(self.cancelButtonX + 1), round(self.cancelButtonY + 1))
dc.DrawBitmap(self.cancelBitmap, round(self.cancelButtonX + cpad), round(self.cancelButtonY + cpad))
dc.SetPen(wx.Pen(sepColor, 1))
dc.DrawLine(0, rect.height - 1, rect.width, rect.height - 1)

View File

@@ -1,9 +1,11 @@
# noinspection PyPackageRequirements
import wx
import config
import gui.mainFrame
from gui.bitmap_loader import BitmapLoader
from gui.preferenceView import PreferenceView
from service.esi import Esi
from service.settings import EsiSettings
# noinspection PyPackageRequirements
@@ -41,38 +43,68 @@ class PFEsiPref(PreferenceView):
"due to 'Signature has expired' error")))
mainSizer.Add(self.enforceJwtExpiration, 0, wx.ALL | wx.EXPAND, 5)
self.ssoServer = wx.CheckBox(panel, wx.ID_ANY, _t("Auto-login (starts local server)"), wx.DefaultPosition,
wx.DefaultSize,
0)
self.ssoServer.SetToolTip(wx.ToolTip(_t("This allows the EVE SSO to callback to your local pyfa instance and complete the authentication process without manual intervention.")))
mainSizer.Add(self.ssoServer, 0, wx.ALL | wx.EXPAND, 5)
rbSizer = wx.BoxSizer(wx.HORIZONTAL)
self.rbMode = wx.RadioBox(panel, -1, _t("Login Authentication Method"), wx.DefaultPosition, wx.DefaultSize,
[_t('Local Server'), _t('Manual')], 1, wx.RA_SPECIFY_COLS)
self.rbMode.SetItemToolTip(0, _t("This option starts a local webserver that EVE SSO Server will call back to"
" with information about the character login."))
self.rbMode.SetItemToolTip(1, _t("This option prompts users to copy and paste information to allow for"
" character login. Use this if having issues with the local server."))
self.rbMode.SetSelection(self.settings.get('loginMode'))
self.enforceJwtExpiration.SetValue(self.settings.get("enforceJwtExpiration" or True))
self.enforceJwtExpiration.SetValue(self.settings.get("enforceJwtExpiration") or True)
self.ssoServer.SetValue(True if self.settings.get("loginMode") == 0 else False)
rbSizer.Add(self.rbMode, 1, wx.TOP | wx.RIGHT, 5)
mainSizer.Add(rbSizer, 0, wx.ALL | wx.EXPAND, 0)
self.rbMode.Bind(wx.EVT_RADIOBOX, self.OnModeChange)
esiSizer = wx.BoxSizer(wx.HORIZONTAL)
self.esiServer = wx.StaticText(panel, wx.ID_ANY, _t("Default SSO Server:"), wx.DefaultPosition, wx.DefaultSize, 0)
self.esiServer.Wrap(-1)
esiSizer.Add(self.esiServer, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
self.esiServer.SetToolTip(wx.ToolTip(_t('The source you choose will be used on connection.')))
self.chESIserver = wx.Choice(panel, choices=list(self.settings.keys()))
self.chESIserver.SetStringSelection(self.settings.get("server"))
esiSizer.Add(self.chESIserver, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 10)
mainSizer.Add(esiSizer, 0, wx.TOP | wx.RIGHT, 10)
self.chESIserver.Bind(wx.EVT_CHOICE, self.OnServerChange)
self.enforceJwtExpiration.Bind(wx.EVT_CHECKBOX, self.OnEnforceChange)
mainSizer.Add(rbSizer, 1, wx.ALL | wx.EXPAND, 0)
self.ssoServer.Bind(wx.EVT_CHECKBOX, self.OnModeChange)
panel.SetSizer(mainSizer)
panel.Layout()
def OnTimeoutChange(self, event):
self.settings.set('timeout', event.GetEventObject().GetValue())
event.Skip()
def OnModeChange(self, event):
self.settings.set('loginMode', event.GetInt())
self.settings.set('loginMode', 0 if self.ssoServer.GetValue() else 1)
event.Skip()
def OnEnforceChange(self, event):
self.settings.set('enforceJwtExpiration', self.enforceJwtExpiration.GetValue())
event.Skip()
def OnServerChange(self, event):
# pass
source = self.chESIserver.GetString(self.chESIserver.GetSelection())
esiService = Esi.getInstance()
# init servers
esiService.init(config.supported_servers[source])
self.settings.set("server", source)
event.Skip()
def getImage(self):
return BitmapLoader.getBitmap("eve", "gui")
PFEsiPref.register()
PFEsiPref.register()

View File

@@ -40,7 +40,7 @@ class PFGeneralPref(PreferenceView):
langSizer = wx.BoxSizer(wx.HORIZONTAL)
self.langChoices = sorted([langInfo for lang, langInfo in LocaleSettings.supported_langauges().items()], key=lambda x: x.Description)
self.langChoices = sorted([langInfo for lang, langInfo in LocaleSettings.supported_languages().items()], key=lambda x: x.Description)
pyfaLangsEnabled = bool(self.langChoices)
if pyfaLangsEnabled:
@@ -64,7 +64,7 @@ class PFGeneralPref(PreferenceView):
langBox.Add(hl.HyperLinkCtrl(panel, -1,
_t("Interested in helping with translations?"),
URL="https://github.com/pyfa-org/Pyfa/blob/master/locale/README.md"
), 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 15)
), 0, wx.LEFT, 15)
else:
self.stLangLabel = wx.StaticText(panel, wx.ID_ANY, _t("Pyfa language selection disabled. Please check if .mo files have been generated.\nRefer to locale/README.md for info."), wx.DefaultPosition, wx.DefaultSize, 0)
self.stLangLabel.Wrap(-1)
@@ -93,7 +93,7 @@ class PFGeneralPref(PreferenceView):
langBox.Add(wx.StaticText(panel, wx.ID_ANY,
_t("Auto will use the same language pyfa uses if available, otherwise English"),
wx.DefaultPosition,
wx.DefaultSize, 0), 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 15)
wx.DefaultSize, 0), 0, wx.LEFT, 15)
self.cbGlobalChar = wx.CheckBox(panel, wx.ID_ANY, _t("Use global character"), wx.DefaultPosition, wx.DefaultSize,
0)

View File

@@ -104,14 +104,14 @@ class CategoryItem(SFBrowserItem):
textColor = colorUtils.GetSuitable(windowColor, 1)
mdc.SetTextForeground(textColor)
mdc.DrawBitmap(self.dropShadowBitmap, self.shipBmpx + 1, self.shipBmpy + 1)
mdc.DrawBitmap(self.shipBmp, self.shipBmpx, self.shipBmpy, 0)
mdc.DrawBitmap(self.dropShadowBitmap, round(self.shipBmpx + 1), round(self.shipBmpy + 1))
mdc.DrawBitmap(self.shipBmp, round(self.shipBmpx), round(self.shipBmpy), 0)
mdc.SetFont(self.fontBig)
categoryName, fittings = self.fittingInfo
mdc.DrawText(categoryName, self.catx, self.caty)
mdc.DrawText(categoryName, round(self.catx), round(self.caty))
# =============================================================================

View File

@@ -416,9 +416,18 @@ class FitItem(SFItem.SFBrowserItem):
if self.dragging:
if not self.dragged:
if self.dragMotionTrigger < 0:
if not self.dragTLFBmp:
tdc = wx.MemoryDC()
bmpWidth = self.toolbarx if self.toolbarx < 200 else 200
self.dragTLFBmp = wx.Bitmap(round(bmpWidth), round(self.GetRect().height))
tdc.SelectObject(self.dragTLFBmp)
tdc.SetBrush(wx.Brush(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)))
tdc.DrawRectangle(0, 0, bmpWidth, self.GetRect().height)
self.DrawItem(tdc)
tdc.SelectObject(wx.NullBitmap)
if not self.HasCapture():
self.CaptureMouse()
self.dragWindow = PFBitmapFrame(self, pos, self.dragTLFBmp)
self.dragWindow = PFBitmapFrame(self, pos, self.dragTLFBmp)
self.dragWindow.Show()
self.dragged = True
self.dragMotionTrigger = self.dragMotionTrail
@@ -493,9 +502,9 @@ class FitItem(SFItem.SFBrowserItem):
else:
shipEffBk = self.shipEffBk
mdc.DrawBitmap(shipEffBk, self.shipEffx, self.shipEffy, 0)
mdc.DrawBitmap(shipEffBk, round(self.shipEffx), round(self.shipEffy), 0)
mdc.DrawBitmap(self.shipBmp, self.shipBmpx, self.shipBmpy, 0)
mdc.DrawBitmap(self.shipBmp, round(self.shipBmpx), round(self.shipBmpy), 0)
mdc.SetFont(self.fontNormal)
@@ -504,26 +513,21 @@ class FitItem(SFItem.SFBrowserItem):
pfdate = drawUtils.GetPartialText(mdc, fitLocalDate,
self.toolbarx - self.textStartx - self.padding * 2 - self.thoverw)
mdc.DrawText(pfdate, self.textStartx, self.timestampy)
mdc.DrawText(pfdate, round(self.textStartx), round(self.timestampy))
mdc.SetFont(self.fontSmall)
mdc.DrawText(self.toolbar.hoverLabel, self.thoverx, self.thovery)
mdc.DrawText(self.toolbar.hoverLabel, round(self.thoverx), round(self.thovery))
mdc.SetFont(self.fontBig)
psname = drawUtils.GetPartialText(mdc, self.fitName,
self.toolbarx - self.textStartx - self.padding * 2 - self.thoverw)
mdc.DrawText(psname, self.textStartx, self.fitNamey)
mdc.DrawText(psname, round(self.textStartx), round(self.fitNamey))
if self.tcFitName.IsShown():
self.AdjustControlSizePos(self.tcFitName, self.textStartx, self.toolbarx - self.editWidth - self.padding)
tdc = wx.MemoryDC()
self.dragTLFBmp = wx.Bitmap((self.toolbarx if self.toolbarx < 200 else 200), rect.height, 24)
tdc.SelectObject(self.dragTLFBmp)
tdc.Blit(0, 0, (self.toolbarx if self.toolbarx < 200 else 200), rect.height, mdc, 0, 0, wx.COPY)
tdc.SelectObject(wx.NullBitmap)
def AdjustControlSizePos(self, editCtl, start, end):
fnEditSize = editCtl.GetSize()

View File

@@ -231,7 +231,7 @@ class NavigationPanel(SFItem.SFBrowserItem):
self.toolbar.SetPosition((self.toolbarx, self.toolbary))
mdc.SetFont(self.fontSmall)
mdc.DrawText(self.toolbar.hoverLabel, self.thoverx, self.thovery)
mdc.DrawText(self.toolbar.hoverLabel, round(self.thoverx), round(self.thovery))
mdc.SetPen(wx.Pen(sepColor, 1))
mdc.DrawLine(0, rect.height - 1, rect.width, rect.height - 1)

View File

@@ -55,7 +55,7 @@ class PFBitmapFrame(wx.Frame):
# todo: evaluate wx.DragImage, might make this class obsolete, however might also lose our customizations
# (like the sexy fade-in animation)
rect = self.GetRect()
canvas = wx.Bitmap(rect.width, rect.height)
canvas = wx.Bitmap(round(rect.width), round(rect.height))
# todo: convert to context manager after updating to wxPython >v4.0.1 (4.0.1 has a bug, see #1421)
# See #1418 for discussion
mdc = wx.BufferedPaintDC(self)
@@ -63,4 +63,4 @@ class PFBitmapFrame(wx.Frame):
mdc.DrawBitmap(self.bitmap, 0, 0)
mdc.SetPen(wx.Pen("#000000", width=1))
mdc.SetBrush(wx.TRANSPARENT_BRUSH)
mdc.DrawRectangle(0, 0, rect.width, rect.height)
mdc.DrawRectangle(0, 0, round(rect.width), round(rect.height))

View File

@@ -57,7 +57,7 @@ class PFListPane(wx.ScrolledWindow):
posy = self.GetScrollPos(wx.VERTICAL)
posy -= self.itemsHeight
self.Scroll(0, posy)
self.Scroll(0, round(posy))
event.Skip()
@@ -65,7 +65,7 @@ class PFListPane(wx.ScrolledWindow):
posy = self.GetScrollPos(wx.VERTICAL)
posy += self.itemsHeight
self.Scroll(0, posy)
self.Scroll(0, round(posy))
event.Skip()
@@ -109,7 +109,7 @@ class PFListPane(wx.ScrolledWindow):
# if we need to adjust
if new_vs_x != -1 or new_vs_y != -1:
self.Scroll(new_vs_x, new_vs_y)
self.Scroll(round(new_vs_x), round(new_vs_y))
def AddWidget(self, widget):
widget.Reparent(self)
@@ -163,6 +163,10 @@ class PFListPane(wx.ScrolledWindow):
def RemoveAllChildren(self):
for widget in self._wList:
widget.Destroy()
# this forces the garbage collector to work properly by removing dangling references to objects which are still alive, otherwise widget cannot be gc-ed eventually causing GDI id exhaustion and crash
for i in widget.__dict__.keys():
widget.__dict__[i] =None
del widget
self.Scroll(0, 0)
self._wList = []

View File

@@ -68,7 +68,7 @@ class RaceSelector(wx.Window):
img = img.Rotate90(False)
img.Replace(0, 0, 0, sysTextColour[0], sysTextColour[1], sysTextColour[2])
if layout == wx.VERTICAL:
img = img.Scale(self.minWidth, 8, wx.IMAGE_QUALITY_HIGH)
img = img.Scale(round(self.minWidth), 8, wx.IMAGE_QUALITY_HIGH)
self.bmpArrow = wx.Bitmap(img)
@@ -194,25 +194,25 @@ class RaceSelector(wx.Window):
bmp = wx.Bitmap(img)
if self.layout == wx.VERTICAL:
mdc.DrawBitmap(dropShadow, rect.width - self.buttonsPadding - bmp.GetWidth() + 1, y + 1)
mdc.DrawBitmap(bmp, rect.width - self.buttonsPadding - bmp.GetWidth(), y)
mdc.DrawBitmap(dropShadow, round(rect.width - self.buttonsPadding - bmp.GetWidth() + 1), round(y + 1))
mdc.DrawBitmap(bmp, round(rect.width - self.buttonsPadding - bmp.GetWidth()), round(y))
y += raceBmp.GetHeight() + self.buttonsPadding
mdc.SetPen(wx.Pen(sepColor, 1))
mdc.DrawLine(rect.width - 1, 0, rect.width - 1, rect.height)
else:
mdc.DrawBitmap(dropShadow, x + 1, self.buttonsPadding + 1)
mdc.DrawBitmap(bmp, x, self.buttonsPadding)
mdc.DrawBitmap(dropShadow, round(x + 1), round(self.buttonsPadding + 1))
mdc.DrawBitmap(bmp, round(x), round(self.buttonsPadding))
x += raceBmp.GetWidth() + self.buttonsPadding
mdc.SetPen(wx.Pen(sepColor, 1))
mdc.DrawLine(0, 0, rect.width, 0)
if self.direction < 1:
if self.layout == wx.VERTICAL:
mdc.DrawBitmap(self.bmpArrow, -2, (rect.height - self.bmpArrow.GetHeight()) / 2)
mdc.DrawBitmap(self.bmpArrow, -2, round((rect.height - self.bmpArrow.GetHeight()) / 2))
else:
mdc.SetPen(wx.Pen(sepColor, 1))
mdc.DrawLine(0, 0, rect.width, 0)
mdc.DrawBitmap(self.bmpArrow, (rect.width - self.bmpArrow.GetWidth()) / 2, -2)
mdc.DrawBitmap(self.bmpArrow, round((rect.width - self.bmpArrow.GetWidth()) / 2), -2)
def OnTimer(self, event):
if event.GetId() == self.animTimerID:

View File

@@ -233,8 +233,8 @@ class PFToolbar:
bmpWidth = bmp.GetWidth()
pdc.DrawBitmap(dropShadowBmp, bx + self.padding / 2, self.toolbarY + self.padding / 2)
pdc.DrawBitmap(bmp, tbx, by)
pdc.DrawBitmap(dropShadowBmp, round(bx + self.padding / 2), round(self.toolbarY + self.padding / 2))
pdc.DrawBitmap(bmp, round(tbx), round(by))
bx += bmpWidth + self.padding

View File

@@ -247,12 +247,12 @@ class ShipItem(SFItem.SFBrowserItem):
else:
shipEffBk = self.shipEffBk
mdc.DrawBitmap(shipEffBk, self.shipEffx, self.shipEffy, 0)
mdc.DrawBitmap(shipEffBk, round(self.shipEffx), round(self.shipEffy), 0)
mdc.DrawBitmap(self.shipBmp, self.shipBmpx, self.shipBmpy, 0)
mdc.DrawBitmap(self.shipBmp, round(self.shipBmpx), round(self.shipBmpy), 0)
mdc.DrawBitmap(self.raceDropShadowBmp, self.raceBmpx + 1, self.raceBmpy + 1)
mdc.DrawBitmap(self.raceBmp, self.raceBmpx, self.raceBmpy)
mdc.DrawBitmap(self.raceDropShadowBmp, round(self.raceBmpx + 1), round(self.raceBmpy + 1))
mdc.DrawBitmap(self.raceBmp, round(self.raceBmpx), round(self.raceBmpy))
shipName, shipTrait, fittings = self.shipFittingInfo
@@ -264,17 +264,17 @@ class ShipItem(SFItem.SFBrowserItem):
fformat = "%d fits"
mdc.SetFont(self.fontNormal)
mdc.DrawText(fformat % fittings if fittings > 0 else fformat, self.textStartx, self.fittingsy)
mdc.DrawText(fformat % fittings if fittings > 0 else fformat, round(self.textStartx), round(self.fittingsy))
mdc.SetFont(self.fontSmall)
mdc.DrawText(self.toolbar.hoverLabel, self.thoverx, self.thovery)
mdc.DrawText(self.toolbar.hoverLabel, round(self.thoverx), round(self.thovery))
mdc.SetFont(self.fontBig)
psname = drawUtils.GetPartialText(mdc, shipName,
self.toolbarx - self.textStartx - self.padding * 2 - self.thoverw)
mdc.DrawText(psname, self.textStartx, self.shipNamey)
mdc.DrawText(psname, round(self.textStartx), round(self.shipNamey))
if self.tcFitName.IsShown():
self.AdjustControlSizePos(self.tcFitName, self.textStartx, self.toolbarx - self.editWidth - self.padding)

View File

@@ -173,7 +173,7 @@ class FirepowerViewFull(StatsView):
if hasSpool:
lines.append("")
lines.append(_t("Current") + ": {}".format(formatAmount(normal.total, prec, lowest, highest)))
for dmgType in normal.names():
for dmgType in normal.names(includePure=True):
val = getattr(normal, dmgType, None)
if val:
lines.append("{}{}: {}%".format(
@@ -215,13 +215,13 @@ class FirepowerViewFull(StatsView):
val = val() if fit is not None else None
preSpoolVal = preSpoolVal() if fit is not None else None
fullSpoolVal = fullSpoolVal() if fit is not None else None
if self._cachedValues[counter] != val:
if self._cachedValues[counter] != getattr(val, 'total', None):
tooltipText = dpsToolTip(val, preSpoolVal, fullSpoolVal, prec, lowest, highest)
label.SetLabel(valueFormat.format(
formatAmount(0 if val is None else val.total, prec, lowest, highest),
"\u02e2" if hasSpoolUp(preSpoolVal, fullSpoolVal) else ""))
label.SetToolTip(wx.ToolTip(tooltipText))
self._cachedValues[counter] = val
self._cachedValues[counter] = getattr(val, 'total', None)
counter += 1
self.panel.Layout()

View File

@@ -146,8 +146,8 @@ class ResistancesViewFull(StatsView):
lbl = PyGauge(contentPanel, font, 100)
lbl.SetMinSize((48, 16))
lbl.SetBackgroundColour(wx.Colour(bc[0], bc[1], bc[2]))
lbl.SetBarColour(wx.Colour(fc[0], fc[1], fc[2]))
lbl.SetBackgroundColour(wx.Colour(round(bc[0]), round(bc[1]), round(bc[2])))
lbl.SetBarColour(wx.Colour(round(fc[0]), round(fc[1]), round(fc[2])))
lbl.SetBarGradient()
lbl.SetFractionDigits(1)

View File

@@ -112,6 +112,7 @@ class TargetingMiscViewMinimal(StatsView):
cargoNamesOrder = OrderedDict((
("fleetHangarCapacity", _t("Fleet hangar")),
("shipMaintenanceBayCapacity", _t("Maintenance bay")),
("specialColonyResourcesHoldCapacity", _t("Infrastructure hold")),
("specialAmmoHoldCapacity", _t("Ammo hold")),
("specialFuelBayCapacity", _t("Fuel bay")),
("specialShipHoldCapacity", _t("Ship hold")),
@@ -134,6 +135,7 @@ class TargetingMiscViewMinimal(StatsView):
cargoValues = {
"main": lambda: fit.ship.getModifiedItemAttr("capacity"),
"fleetHangarCapacity": lambda: fit.ship.getModifiedItemAttr("fleetHangarCapacity"),
"specialColonyResourcesHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialColonyResourcesHoldCapacity"),
"shipMaintenanceBayCapacity": lambda: fit.ship.getModifiedItemAttr("shipMaintenanceBayCapacity"),
"specialAmmoHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialAmmoHoldCapacity"),
"specialFuelBayCapacity": lambda: fit.ship.getModifiedItemAttr("specialFuelBayCapacity"),

View File

@@ -197,6 +197,30 @@ class SignatureRadiusColumn(GraphColumn):
SignatureRadiusColumn.register()
class FullHpColumn(GraphColumn):
name = 'FullHP'
stickPrefixToValue = True
def __init__(self, fittingView, params):
super().__init__(fittingView, 68)
def _getValue(self, stuff):
if isinstance(stuff, Fit):
full_hp = stuff.hp.get('shield', 0) + stuff.hp.get('armor', 0) + stuff.hp.get('hull', 0)
elif isinstance(stuff, TargetProfile):
full_hp = stuff.hp
else:
full_hp = 0
return full_hp, 'hp'
def _getFitTooltip(self):
return 'Total raw HP'
FullHpColumn.register()
class ShieldAmountColumn(GraphColumn):
name = 'ShieldAmount'

View File

@@ -93,8 +93,6 @@ class Miscellanea(ViewColumn):
text = "{} dmg".format(formatAmount(dmg, 3, 0, 6))
tooltip = "Raw damage done"
return text, tooltip
pass
elif itemGroup in ("Energy Weapon", "Hybrid Weapon", "Projectile Weapon", "Combat Drone", "Fighter Drone"):
trackingSpeed = stuff.getModifiedItemAttr("trackingSpeed")
optimalSig = stuff.getModifiedItemAttr("optimalSigRadius")
@@ -167,7 +165,7 @@ class Miscellanea(ViewColumn):
text = "{0}/s".format(formatAmount(capPerSec, 3, 0, 3))
tooltip = "Energy neutralization per second"
return text, tooltip
elif itemGroup == "Salvager":
elif itemGroup in ("Salvager", "Salvage Drone"):
chance = stuff.getModifiedItemAttr("accessDifficultyBonus")
if not chance:
return "", None
@@ -590,7 +588,7 @@ class Miscellanea(ViewColumn):
text = "{0}/s".format(formatAmount(capPerSec, 3, 0, 3))
tooltip = "Energy neutralization per second"
return text, tooltip
elif itemGroup in ("Micro Jump Drive", "Micro Jump Field Generators"):
elif itemGroup in ("Micro Jump Drive", "Micro Jump Field Generators", "Capital Mobility Modules"):
cycleTime = stuff.getModifiedItemAttr("duration") / 1000
text = "{0}s".format(formatAmount(cycleTime, 3, 0, 3))
tooltip = "Spoolup time"
@@ -810,6 +808,19 @@ class Miscellanea(ViewColumn):
text = "{}".format(formatAmount(scanStr, 4, 0, 3))
tooltip = "Scan strength at {} AU scan range".format(formatAmount(baseRange, 3, 0, 0))
return text, tooltip
elif chargeGroup in ("SCARAB Breacher Pods",):
duration = stuff.getModifiedChargeAttr("dotDuration") / 1000
dmgAbs = stuff.getModifiedChargeAttr("dotMaxDamagePerTick")
dmgRel = stuff.getModifiedChargeAttr("dotMaxHPPercentagePerTick")
text = "{}/{}% over {}s".format(
formatAmount(dmgAbs * duration, 3, 0, 6),
formatAmount(dmgRel * duration, 3, 0, 6),
formatAmount(duration, 0, 0, 0))
fullDmgHp = dmgAbs / (dmgRel / 100)
tooltip = (
'Pure damage inflicted over time, minimum of absolute / relative\n'
'Full DPS from {} target HP').format(formatAmount(fullDmgHp, 3, 0, 6))
return text, tooltip
else:
return "", None
else:

View File

@@ -39,9 +39,10 @@ from gui.builtinViewColumns.state import State
from gui.chrome_tabs import EVT_NOTEBOOK_PAGE_CHANGED
from gui.contextMenu import ContextMenu
from gui.utils.staticHelpers import DragDropHelper
from gui.utils.dark import isDark
from service.fit import Fit
from service.market import Market
from config import slotColourMap
from config import slotColourMap, slotColourMapDark, errColor, errColorDark
from gui.fitCommands.helpers import getSimilarModPositions
pyfalog = Logger(__name__)
@@ -126,6 +127,10 @@ class FittingViewDrop(wx.DropTarget):
if self.GetData():
dragged_data = DragDropHelper.data
# pyfalog.debug("fittingView: recieved drag: " + self.dropData.GetText())
if dragged_data is None:
return t
data = dragged_data.split(':')
self.dropFn(x, y, data)
return t
@@ -729,7 +734,10 @@ class FittingView(d.Display):
event.Skip()
def slotColour(self, slot):
return slotColourMap.get(slot) or self.GetBackgroundColour()
if isDark():
return slotColourMapDark.get(slot) or self.GetBackgroundColour()
else:
return slotColourMap.get(slot) or self.GetBackgroundColour()
def refresh(self, stuff):
"""
@@ -774,7 +782,7 @@ class FittingView(d.Display):
if slotMap[mod.slot] or hasRestrictionOverriden: # Color too many modules as red
self.SetItemBackgroundColour(i, wx.Colour(204, 51, 51))
self.SetItemBackgroundColour(i, errColorDark if isDark() else errColor)
elif sFit.serviceFittingOptions["colorFitBySlot"]: # Color by slot it enabled
self.SetItemBackgroundColour(i, self.slotColour(mod.slot))
@@ -895,7 +903,7 @@ class FittingView(d.Display):
opts.m_labelText = name
if imgId != -1:
opts.m_labelBitmap = wx.Bitmap(isize, isize)
opts.m_labelBitmap = wx.Bitmap(round(isize), round(isize))
width = render.DrawHeaderButton(self, tdc, (0, 0, 16, 16), sortArrow=wx.HDR_SORT_ICON_NONE, params=opts)
@@ -911,7 +919,7 @@ class FittingView(d.Display):
maxWidth += columnsWidths[i]
mdc = wx.MemoryDC()
mbmp = wx.Bitmap(maxWidth, maxRowHeight * rows + padding * 4 + headerSize)
mbmp = wx.Bitmap(round(maxWidth), round(maxRowHeight * rows + padding * 4 + headerSize))
mdc.SelectObject(mbmp)
@@ -956,7 +964,7 @@ class FittingView(d.Display):
cx = padding
if slotMap[st.slot]:
mdc.DrawRectangle(cx, cy, maxWidth - cx, maxRowHeight)
mdc.DrawRectangle(round(cx), round(cy), round(maxWidth - cx), round(maxRowHeight))
for i, col in enumerate(self.activeColumns):
if i > maxColumns:

View File

@@ -13,8 +13,8 @@ from service.market import Market
def stripHtml(text):
text = re.sub('<\s*br\s*/?\s*>', '\n', text)
text = re.sub('</?[^/]+?(/\s*)?>', '', text)
text = re.sub(r'<\s*br\s*/?\s*>', '\n', text)
text = re.sub(r'</?[^/]+?(/\s*)?>', '', text)
return text

View File

@@ -115,7 +115,7 @@ class CharacterEntityEditor(EntityEditor):
sChar = Character.getInstance()
if entity.alphaCloneID:
trimmed_name = re.sub('[ \(\u03B1\)]+$', '', name)
trimmed_name = re.sub('[ \\(\u03B1\\)]+$', '', name)
sChar.rename(entity, trimmed_name)
else:
sChar.rename(entity, name)
@@ -371,7 +371,7 @@ class SkillTreeView(wx.Panel):
bSizerButtons.AddStretchSpacer()
importExport = ((_t("Import skills from clipboard"), wx.ART_FILE_OPEN, "import"),
(_t("Export skills from clipboard"), wx.ART_FILE_SAVE_AS, "export"))
(_t("Export skills to clipboard"), wx.ART_FILE_SAVE_AS, "export"))
for tooltip, art, attr in importExport:
bitmap = wx.ArtProvider.GetBitmap(art, wx.ART_BUTTON)
@@ -446,6 +446,7 @@ class SkillTreeView(wx.Panel):
text = fromClipboard().strip()
if text:
sCharacter = Character.getInstance()
char = self.charEditor.entityEditor.getActiveEntity()
try:
lines = text.splitlines()
@@ -455,7 +456,7 @@ class SkillTreeView(wx.Panel):
skill, level = s.rsplit(None, 1)[0], arabicOrRomanToInt(s.rsplit(None, 1)[1])
skill = char.getSkill(skill)
if skill:
skill.setLevel(level, ignoreRestrict=True)
sCharacter.changeLevel(char.ID, skill.item.ID, level)
except (KeyboardInterrupt, SystemExit):
raise
@@ -480,6 +481,35 @@ class SkillTreeView(wx.Panel):
toClipboard(list)
def exportSkillsSuperCondensed(self, evt):
char = self.charEditor.entityEditor.getActiveEntity()
skills = {}
explicit_levels = {}
implicit_levels = {}
for s in char.__class__.getSkillNameMap().keys():
skill = char.getSkill(s)
if skill.level < 1:
continue
skills[skill.item.ID] = skill
explicit_levels[skill.item.ID] = skill.level
for skill in skills.values():
for req_skill, level in skill.item.requiredSkills.items():
if req_skill.ID not in implicit_levels or implicit_levels[req_skill.ID] < level:
implicit_levels[req_skill.ID] = level
condensed = {}
for typeID, level in explicit_levels.items():
if typeID not in implicit_levels or implicit_levels[typeID] < level:
condensed[skills[typeID].item.name] = level
lines = []
for skill in sorted(condensed):
lines.append(f'{skill}\t{condensed[skill]}')
toClipboard('\n'.join(lines))
def onSecStatus(self, event):
sChar = Character.getInstance()
char = self.charEditor.entityEditor.getActiveEntity()
@@ -516,7 +546,10 @@ class SkillTreeView(wx.Panel):
def populateSkillTreeSkillSearch(self, event=None):
sChar = Character.getInstance()
char = self.charEditor.entityEditor.getActiveEntity()
search = self.searchInput.GetLineText(0)
try:
search = self.searchInput.GetLineText(0)
except AttributeError:
search = self.searchInput.GetValue()
root = self.root
tree = self.skillTreeListCtrl
@@ -530,7 +563,7 @@ class SkillTreeView(wx.Panel):
iconId = self.skillBookDirtyImageId
childId = tree.AppendItem(root, name, iconId, data=('skill', id))
tree.SetItemText(childId, 1, _t("Level {}d").format(int(level)) if isinstance(level, float) else level)
tree.SetItemText(childId, 1, _t("Level {}").format(int(level)) if isinstance(level, float) else level)
def populateSkillTree(self, event=None):
sChar = Character.getInstance()
@@ -588,7 +621,6 @@ class SkillTreeView(wx.Panel):
iconId = self.skillBookDirtyImageId
childId = tree.AppendItem(root, name, iconId, data=('skill', id))
tree.SetItemText(childId, 1, _t("Level {}").format(int(level)) if isinstance(level, float) else level)
def spawnMenu(self, event):
@@ -804,7 +836,12 @@ class APIView(wx.Panel):
self.SetSizer(pmainSizer)
self.Layout()
self.ssoListChanged(None)
try:
self.ssoListChanged(None)
except (KeyboardInterrupt, SystemExit):
raise
except:
pass
def ssoCharChanged(self, event):
sChar = Character.getInstance()
@@ -856,7 +893,7 @@ class APIView(wx.Panel):
noneID = self.charChoice.Append(_t("None"), None)
for char in ssoChars:
currId = self.charChoice.Append(char.characterName, char.ID)
currId = self.charChoice.Append(char.characterDisplay, char.ID)
if sso is not None and char.ID == sso.ID:
self.charChoice.SetSelection(currId)
@@ -910,7 +947,7 @@ class SecStatusDialog(wx.Dialog):
self.m_staticText1.Wrap(-1)
bSizer1.Add(self.m_staticText1, 1, wx.ALL | wx.EXPAND, 5)
self.floatSpin = FloatSpin(self, value=sec, min_val=-5.0, max_val=5.0, increment=0.1, digits=2, size=(-1, -1))
self.floatSpin = FloatSpin(self, value=sec, min_val=-10.0, max_val=5.0, increment=0.1, digits=2, size=(-1, -1))
bSizer1.Add(self.floatSpin, 0, wx.ALIGN_CENTER | wx.ALL, 5)
btnOk = wx.Button(self, wx.ID_OK)

View File

@@ -514,7 +514,7 @@ class _TabRenderer:
Creates the tab background bitmap based upon calculated dimension values
and modified bitmaps via InitBitmaps()
"""
bk_bmp = wx.Bitmap(self.tab_width, self.tab_height)
bk_bmp = wx.Bitmap(round(self.tab_width), round(self.tab_height))
mdc = wx.MemoryDC()
mdc.SelectObject(bk_bmp)
@@ -525,16 +525,16 @@ class _TabRenderer:
# convert middle bitmap and scale to tab width
cm = self.ctab_middle_bmp.ConvertToImage()
mimg = cm.Scale(self.content_width, self.ctab_middle.GetHeight(),
mimg = cm.Scale(round(self.content_width), round(self.ctab_middle.GetHeight()),
wx.IMAGE_QUALITY_NORMAL)
mbmp = wx.Bitmap(mimg)
# draw middle bitmap, offset by left
mdc.DrawBitmap(mbmp, self.left_width, 0)
mdc.DrawBitmap(mbmp, round(self.left_width), 0)
# draw right bitmap offset by left + middle
mdc.DrawBitmap(self.ctab_right_bmp,
self.content_width + self.left_width, 0)
round(self.content_width + self.left_width), 0)
mdc.SelectObject(wx.NullBitmap)
@@ -555,7 +555,7 @@ class _TabRenderer:
+ self.left_width \
- self.ctab_close_bmp.GetWidth() / 2
y_offset = (self.tab_height - self.ctab_close_bmp.GetHeight()) / 2
self.close_region.Offset(x_offset, y_offset)
self.close_region.Offset(round(x_offset), round(y_offset))
def InitColors(self):
"""Determines colors used for tab, based on system settings"""
@@ -573,7 +573,7 @@ class _TabRenderer:
height = self.tab_height
canvas = wx.Bitmap(self.tab_width, self.tab_height, 24)
canvas = wx.Bitmap(round(self.tab_width), round(self.tab_height), 24)
mdc = wx.MemoryDC()
@@ -590,8 +590,8 @@ class _TabRenderer:
# Draw tab icon
mdc.DrawBitmap(
bmp,
self.left_width + self.padding - bmp.GetWidth() / 2,
(height - bmp.GetHeight()) / 2)
round(self.left_width + self.padding - bmp.GetWidth() / 2),
round((height - bmp.GetHeight()) / 2))
# draw close button
if self.closeable:
@@ -604,8 +604,8 @@ class _TabRenderer:
mdc.DrawBitmap(
cbmp,
self.content_width + self.left_width - cbmp.GetWidth() / 2,
(height - cbmp.GetHeight()) / 2)
round(self.content_width + self.left_width - cbmp.GetWidth() / 2),
round((height - cbmp.GetHeight()) / 2))
mdc.SelectObject(wx.NullBitmap)
@@ -640,7 +640,7 @@ class _TabRenderer:
# draw text (with no ellipses)
text = draw.GetPartialText(dc, self.text, maxsize, "")
tx, ty = dc.GetTextExtent(text)
dc.DrawText(text, text_start + self.padding, height / 2 - ty / 2)
dc.DrawText(text, round(text_start + self.padding), round(height / 2 - ty / 2))
def __repr__(self):
return "_TabRenderer(text={}, disabled={}) at {}".format(
@@ -1005,7 +1005,7 @@ class _TabsContainer(wx.Panel):
region = tab.GetCloseButtonRegion()
posx, posy = tab.GetPosition()
region.Offset(posx, posy)
region.Offset(round(posx), round(posy))
if region.Contains(x, y):
index = self.tabs.index(tab)
@@ -1036,7 +1036,7 @@ class _TabsContainer(wx.Panel):
region = self.add_button.GetRegion()
ax, ay = self.add_button.GetPosition()
region.Offset(ax, ay)
region.Offset(round(ax), round(ay))
if region.Contains(x, y):
ev = PageAdding()
@@ -1058,7 +1058,7 @@ class _TabsContainer(wx.Panel):
for tab in self.tabs:
region = tab.GetCloseButtonRegion()
posx, posy = tab.GetPosition()
region.Offset(posx, posy)
region.Offset(round(posx), round(posy))
if region.Contains(x, y):
if not tab.GetCloseButtonHoverStatus():
@@ -1093,7 +1093,7 @@ class _TabsContainer(wx.Panel):
tabRegion = tab.GetTabRegion()
tabPos = tab.GetPosition()
tabPosX, tabPosY = tabPos
tabRegion.Offset(tabPosX, tabPosY)
tabRegion.Offset(round(tabPosX), round(tabPosY))
if tabRegion.Contains(x, y):
return True
@@ -1166,7 +1166,7 @@ class _TabsContainer(wx.Panel):
region = self.add_button.GetRegion()
ax, ay = self.add_button.GetPosition()
region.Offset(ax, ay)
region.Offset(round(ax), round(ay))
if region.Contains(x, y):
if not self.add_button.IsHighlighted():
@@ -1198,7 +1198,7 @@ class _TabsContainer(wx.Panel):
if self.show_add_button:
ax, ay = self.add_button.GetPosition()
mdc.DrawBitmap(self.add_button.Render(), ax, ay, True)
mdc.DrawBitmap(self.add_button.Render(), round(ax), round(ay), True)
for i in range(len(self.tabs) - 1, -1, -1):
tab = self.tabs[i]
@@ -1206,14 +1206,14 @@ class _TabsContainer(wx.Panel):
if not tab.IsSelected():
# drop shadow first
mdc.DrawBitmap(self.fxBmps[tab], posx, posy, True)
mdc.DrawBitmap(self.fxBmps[tab], round(posx), (posy), True)
bmp = tab.Render()
img = bmp.ConvertToImage()
img = img.AdjustChannels(1, 1, 1, 0.85)
bmp = wx.Bitmap(img)
mdc.DrawBitmap(bmp, posx, posy, True)
mdc.DrawBitmap(bmp, round(posx), (posy), True)
mdc.SetDeviceOrigin(posx, posy)
mdc.SetDeviceOrigin(round(posx), round(posy))
tab.DrawText(mdc)
mdc.SetDeviceOrigin(0, 0)
else:
@@ -1224,7 +1224,7 @@ class _TabsContainer(wx.Panel):
if selected:
posx, posy = selected.GetPosition()
# drop shadow first
mdc.DrawBitmap(self.fxBmps[selected], posx, posy, True)
mdc.DrawBitmap(self.fxBmps[selected], round(posx), round(posy), True)
bmp = selected.Render()
@@ -1233,9 +1233,9 @@ class _TabsContainer(wx.Panel):
img = img.AdjustChannels(1.2, 1.2, 1.2, 0.7)
bmp = wx.Bitmap(img)
mdc.DrawBitmap(bmp, posx, posy, True)
mdc.DrawBitmap(bmp, round(posx), round(posy), True)
mdc.SetDeviceOrigin(posx, posy)
mdc.SetDeviceOrigin(round(posx), round(posy))
selected.DrawText(mdc)
mdc.SetDeviceOrigin(0, 0)
@@ -1501,7 +1501,7 @@ class PFNotebookPagePreview(wx.Frame):
def OnWindowPaint(self, event):
rect = self.GetRect()
canvas = wx.Bitmap(rect.width, rect.height)
canvas = wx.Bitmap(round(rect.width), round(rect.height))
mdc = wx.BufferedPaintDC(self)
mdc.SelectObject(canvas)
color = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)
@@ -1514,7 +1514,7 @@ class PFNotebookPagePreview(wx.Frame):
x, y = mdc.GetTextExtent(self.title)
mdc.SetBrush(wx.Brush(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)))
mdc.DrawRectangle(0, 0, rect.width, 16)
mdc.DrawRectangle(0, 0, round(rect.width), 16)
mdc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))
@@ -1523,4 +1523,4 @@ class PFNotebookPagePreview(wx.Frame):
mdc.SetPen(wx.Pen("#000000", width=1))
mdc.SetBrush(wx.TRANSPARENT_BRUSH)
mdc.DrawRectangle(0, 16, rect.width, rect.height - 16)
mdc.DrawRectangle(0, 16, round(rect.width), round(rect.height - 16))

View File

@@ -193,7 +193,7 @@ class CopySelectDialog(wx.Dialog):
def exportEsi(self, options, callback):
fit = getFit(self.mainFrame.getActiveFit())
Port.exportESI(fit, True, callback)
Port.exportESI(fit, False, False, False, callback)
def exportXml(self, options, callback):
fit = getFit(self.mainFrame.getActiveFit())

View File

@@ -29,8 +29,9 @@ class Display(wx.ListCtrl):
DEFAULT_COLS = None
def __init__(self, parent, size=wx.DefaultSize, style=0):
wx.ListCtrl.__init__(self, parent, size=size, style=wx.LC_REPORT | style)
wx.ListCtrl.__init__(self)
self.EnableSystemTheme(False)
self.Create(parent, size=size, style=wx.LC_REPORT | style)
self.imageList = CachingImageList(16, 16)
self.SetImageList(self.imageList, wx.IMAGE_LIST_SMALL)
self.activeColumns = []

View File

@@ -96,7 +96,7 @@ class EveFittings(AuxiliaryFrame):
self.charChoice.Clear()
for char in chars:
self.charChoice.Append(char.characterName, char.ID)
self.charChoice.Append(char.characterDisplay, char.ID)
if len(chars) > 0:
self.charChoice.SetSelection(0)
@@ -227,21 +227,6 @@ class EveFittings(AuxiliaryFrame):
self.fitView.update([])
class ESIServerExceptionHandler:
def __init__(self, parentWindow, ex):
pyfalog.error(ex)
with wx.MessageDialog(
parentWindow,
_t("There was an issue starting up the localized server, try setting "
"Login Authentication Method to Manual by going to Preferences -> EVE SS0 -> "
"Login Authentication Method. If this doesn't fix the problem please file an "
"issue on Github."),
_t("Add Character Error"),
wx.OK | wx.ICON_ERROR
) as dlg:
dlg.ShowModal()
class ESIExceptionHandler:
# todo: make this a generate excetpion handler for all calls
def __init__(self, ex):
@@ -283,7 +268,7 @@ class ExportToEve(AuxiliaryFrame):
def __init__(self, parent):
super().__init__(
parent, id=wx.ID_ANY, title=_t("Export fit to EVE"), pos=wx.DefaultPosition,
size=wx.Size(400, 140) if "wxGTK" in wx.PlatformInfo else wx.Size(350, 115), resizeable=True)
size=wx.Size(400, 175) if "wxGTK" in wx.PlatformInfo else wx.Size(350, 145), resizeable=True)
self.mainFrame = parent
@@ -305,6 +290,16 @@ class ExportToEve(AuxiliaryFrame):
self.exportChargesCb.Bind(wx.EVT_CHECKBOX, self.OnChargeExportChange)
mainSizer.Add(self.exportChargesCb, 0, 0, 5)
self.exportImplantsCb = wx.CheckBox(self, wx.ID_ANY, _t('Export Implants'), wx.DefaultPosition, wx.DefaultSize, 0)
self.exportImplantsCb.SetValue(EsiSettings.getInstance().get('exportImplants'))
self.exportImplantsCb.Bind(wx.EVT_CHECKBOX, self.OnImplantsExportChange)
mainSizer.Add(self.exportImplantsCb, 0, 0, 5)
self.exportBoostersCb = wx.CheckBox(self, wx.ID_ANY, _t('Export Boosters'), wx.DefaultPosition, wx.DefaultSize, 0)
self.exportBoostersCb.SetValue(EsiSettings.getInstance().get('exportBoosters'))
self.exportBoostersCb.Bind(wx.EVT_CHECKBOX, self.OnBoostersExportChange)
mainSizer.Add(self.exportBoostersCb, 0, 0, 5)
self.exportBtn.Bind(wx.EVT_BUTTON, self.exportFitting)
self.statusbar = wx.StatusBar(self)
@@ -324,13 +319,21 @@ class ExportToEve(AuxiliaryFrame):
EsiSettings.getInstance().set('exportCharges', self.exportChargesCb.GetValue())
event.Skip()
def OnImplantsExportChange(self, event):
EsiSettings.getInstance().set('exportImplants', self.exportImplantsCb.GetValue())
event.Skip()
def OnBoostersExportChange(self, event):
EsiSettings.getInstance().set('exportBoosters', self.exportBoostersCb.GetValue())
event.Skip()
def updateCharList(self):
sEsi = Esi.getInstance()
chars = sEsi.getSsoCharacters()
self.charChoice.Clear()
for char in chars:
self.charChoice.Append(char.characterName, char.ID)
self.charChoice.Append(char.characterDisplay, char.ID)
if len(chars) > 0:
self.charChoice.SetSelection(0)
@@ -360,8 +363,10 @@ class ExportToEve(AuxiliaryFrame):
sFit = Fit.getInstance()
exportCharges = self.exportChargesCb.GetValue()
exportImplants = self.exportImplantsCb.GetValue()
exportBoosters = self.exportBoostersCb.GetValue()
try:
data = sPort.exportESI(sFit.getFit(fitID), exportCharges)
data = sPort.exportESI(sFit.getFit(fitID), exportCharges, exportImplants, exportBoosters)
except ESIExportException as e:
msg = str(e)
if not msg:
@@ -414,6 +419,7 @@ class SsoCharacterMgmt(AuxiliaryFrame):
self.lcCharacters.InsertColumn(0, heading=_t('Character'))
self.lcCharacters.InsertColumn(1, heading=_t('Character ID'))
self.lcCharacters.InsertColumn(2, heading=_t('Server'))
self.popCharList()
@@ -476,9 +482,11 @@ class SsoCharacterMgmt(AuxiliaryFrame):
self.lcCharacters.InsertItem(index, char.characterName)
self.lcCharacters.SetItem(index, 1, str(char.characterID))
self.lcCharacters.SetItemData(index, char.ID)
self.lcCharacters.SetItem(index, 2, char.server or "<unknown>")
self.lcCharacters.SetColumnWidth(0, wx.LIST_AUTOSIZE)
self.lcCharacters.SetColumnWidth(1, wx.LIST_AUTOSIZE)
self.lcCharacters.SetColumnWidth(2, wx.LIST_AUTOSIZE)
def addChar(self, event):
try:
@@ -486,8 +494,6 @@ class SsoCharacterMgmt(AuxiliaryFrame):
sEsi.login()
except (KeyboardInterrupt, SystemExit):
raise
except Exception as ex:
ESIServerExceptionHandler(self, ex)
def delChar(self, event):
item = self.lcCharacters.GetFirstSelected()

View File

@@ -112,7 +112,7 @@ class FitBrowserLiteDialog(wx.Dialog):
return True
matches = []
searchTokens = [t.lower() for t in re.split('\s+', searchPattern)]
searchTokens = [t.lower() for t in re.split(r'\s+', searchPattern)]
for fit in self.allFits:
if isMatch(fit, searchTokens):
matches.append(fit)

View File

@@ -12,6 +12,7 @@ from .gui.cargo.remove import GuiRemoveCargosCommand
from .gui.commandFit.add import GuiAddCommandFitsCommand
from .gui.commandFit.remove import GuiRemoveCommandFitsCommand
from .gui.commandFit.toggleStates import GuiToggleCommandFitStatesCommand
from .gui.fitPilotSecurity import GuiChangeFitPilotSecurityCommand
from .gui.fitRename import GuiRenameFitCommand
from .gui.fitRestrictionToggle import GuiToggleFittingRestrictionsCommand
from .gui.fitSystemSecurity import GuiChangeFitSystemSecurityCommand

View File

@@ -0,0 +1,32 @@
import wx
from logbook import Logger
from service.fit import Fit
pyfalog = Logger(__name__)
class CalcChangeFitPilotSecurityCommand(wx.Command):
def __init__(self, fitID, secStatus):
wx.Command.__init__(self, True, 'Change Fit Pilot Security')
self.fitID = fitID
self.secStatus = secStatus
self.savedSecStatus = None
def Do(self):
pyfalog.debug('Doing changing pilot security status of fit {} to {}'.format(self.fitID, self.secStatus))
fit = Fit.getInstance().getFit(self.fitID, basic=True)
# Fetching status via getter and then saving 'raw' security status
# is intentional, to restore pre-change state properly
if fit.pilotSecurity == self.secStatus:
return False
self.savedSecStatus = fit.pilotSecurity
fit.pilotSecurity = self.secStatus
return True
def Undo(self):
pyfalog.debug('Undoing changing pilot security status of fit {} to {}'.format(self.fitID, self.secStatus))
cmd = CalcChangeFitPilotSecurityCommand(fitID=self.fitID, secStatus=self.savedSecStatus)
return cmd.Do()

View File

@@ -0,0 +1,36 @@
import wx
from service.fit import Fit
import eos.db
import gui.mainFrame
from gui import globalEvents as GE
from gui.fitCommands.helpers import InternalCommandHistory
from gui.fitCommands.calc.fitPilotSecurity import CalcChangeFitPilotSecurityCommand
class GuiChangeFitPilotSecurityCommand(wx.Command):
def __init__(self, fitID, secStatus):
wx.Command.__init__(self, True, 'Change Fit Pilot Security')
self.internalHistory = InternalCommandHistory()
self.fitID = fitID
self.secStatus = secStatus
def Do(self):
cmd = CalcChangeFitPilotSecurityCommand(fitID=self.fitID, secStatus=self.secStatus)
success = self.internalHistory.submit(cmd)
eos.db.flush()
sFit = Fit.getInstance()
sFit.recalc(self.fitID)
eos.db.commit()
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,)))
return success
def Undo(self):
success = self.internalHistory.undoAll()
eos.db.flush()
sFit = Fit.getInstance()
sFit.recalc(self.fitID)
eos.db.commit()
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,)))
return success

View File

@@ -61,10 +61,11 @@ from gui.statsPane import StatsPane
from gui.targetProfileEditor import TargetProfileEditor
from gui.updateDialog import UpdateDialog
from gui.utils.clipboard import fromClipboard
from gui.utils.progressHelper import ProgressHelper
from service.character import Character
from service.esi import Esi
from service.fit import Fit
from service.port import IPortUser, Port
from service.port import Port
from service.price import Price
from service.settings import HTMLExportSettings, SettingsProvider
from service.update import Update
@@ -130,7 +131,6 @@ class OpenFitsThread(threading.Thread):
self.running = False
# todo: include IPortUser again
class MainFrame(wx.Frame):
__instance = None
@@ -845,14 +845,15 @@ class MainFrame(wx.Frame):
style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST | wx.FD_MULTIPLE
) as dlg:
if dlg.ShowModal() == wx.ID_OK:
self.progressDialog = wx.ProgressDialog(
_t("Importing fits"),
" " * 100, # set some arbitrary spacing to create width in window
parent=self,
style=wx.PD_CAN_ABORT | wx.PD_SMOOTH | wx.PD_ELAPSED_TIME | wx.PD_APP_MODAL
)
Port.importFitsThreaded(dlg.GetPaths(), self)
self.progressDialog.ShowModal()
# set some arbitrary spacing to create width in window
progress = ProgressHelper(message=" " * 100, callback=self._openAfterImport)
call = (Port.importFitsThreaded, [dlg.GetPaths(), progress], {})
self.handleProgress(
title=_t("Importing fits"),
style=wx.PD_CAN_ABORT | wx.PD_SMOOTH | wx.PD_APP_MODAL | wx.PD_AUTO_HIDE,
call=call,
progress=progress,
errMsgLbl=_t("Import Error"))
def backupToXml(self, event):
""" Back up all fits to EVE XML file """
@@ -863,32 +864,30 @@ class MainFrame(wx.Frame):
_t("Save Backup As..."),
wildcard=_t("EVE XML fitting file") + " (*.xml)|*.xml",
style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT,
defaultFile=defaultFile,
) as dlg:
if dlg.ShowModal() == wx.ID_OK:
filePath = dlg.GetPath()
defaultFile=defaultFile) as fileDlg:
if fileDlg.ShowModal() == wx.ID_OK:
filePath = fileDlg.GetPath()
if '.' not in os.path.basename(filePath):
filePath += ".xml"
sFit = Fit.getInstance()
max_ = sFit.countAllFits()
self.progressDialog = wx.ProgressDialog(
_t("Backup fits"),
_t("Backing up {} fits to: {}").format(max_, filePath),
maximum=max_,
parent=self,
style=wx.PD_CAN_ABORT | wx.PD_SMOOTH | wx.PD_ELAPSED_TIME | wx.PD_APP_MODAL
)
Port.backupFits(filePath, self)
self.progressDialog.ShowModal()
fitAmount = Fit.getInstance().countAllFits()
progress = ProgressHelper(
message=_t("Backing up {} fits to: {}").format(fitAmount, filePath),
maximum=fitAmount + 1)
call = (Port.backupFits, [filePath, progress], {})
self.handleProgress(
title=_t("Backup fits"),
style=wx.PD_CAN_ABORT | wx.PD_SMOOTH | wx.PD_ELAPSED_TIME | wx.PD_APP_MODAL | wx.PD_AUTO_HIDE,
call=call,
progress=progress,
errMsgLbl=_t("Export Error"))
def exportHtml(self, event):
from gui.utils.exportHtml import exportHtml
sFit = Fit.getInstance()
settings = HTMLExportSettings.getInstance()
max_ = sFit.countAllFits()
path = settings.getPath()
if not os.path.isdir(os.path.dirname(path)):
@@ -903,82 +902,44 @@ class MainFrame(wx.Frame):
) as dlg:
if dlg.ShowModal() == wx.ID_OK:
return
progress = ProgressHelper(
message=_t("Generating HTML file at: {}").format(path),
maximum=sFit.countAllFits() + 1)
call = (exportHtml.getInstance().refreshFittingHtml, [True, progress], {})
self.handleProgress(
title=_t("Backup fits"),
style=wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME,
call=call,
progress=progress)
self.progressDialog = wx.ProgressDialog(
_t("Backup fits"),
_t("Generating HTML file at: {}").format(path),
maximum=max_, parent=self,
style=wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME)
exportHtml.getInstance().refreshFittingHtml(True, self.backupCallback)
self.progressDialog.ShowModal()
def backupCallback(self, info):
if info == -1:
self.closeProgressDialog()
else:
self.progressDialog.Update(info)
def on_port_process_start(self):
# flag for progress dialog.
self.__progress_flag = True
def on_port_processing(self, action, data=None):
# 2017/03/29 NOTE: implementation like interface
wx.CallAfter(
self._on_port_processing, action, data
)
return self.__progress_flag
def _on_port_processing(self, action, data):
"""
While importing fits from file, the logic calls back to this function to
update progress bar to show activity. XML files can contain multiple
ships with multiple fits, whereas EFT cfg files contain many fits of
a single ship. When iterating through the files, we update the message
when we start a new file, and then Pulse the progress bar with every fit
that is processed.
action : a flag that lets us know how to deal with :data
None: Pulse the progress bar
1: Replace message with data
other: Close dialog and handle based on :action (-1 open fits, -2 display error)
"""
_message = None
if action & IPortUser.ID_ERROR:
self.closeProgressDialog()
_message = _t("Import Error") if action & IPortUser.PROCESS_IMPORT else _t("Export Error")
def handleProgress(self, title, style, call, progress, errMsgLbl=None):
extraArgs = {}
if progress.maximum is not None:
extraArgs['maximum'] = progress.maximum
with wx.ProgressDialog(
parent=self,
title=title,
message=progress.message,
style=style,
**extraArgs
) as dlg:
func, args, kwargs = call
func(*args, **kwargs)
while progress.working:
wx.MilliSleep(250)
wx.Yield()
(progress.dlgWorking, skip) = dlg.Update(progress.current, progress.message)
if progress.error and errMsgLbl:
with wx.MessageDialog(
self,
_t("The following error was generated") +
f"\n\n{data}\n\n" +
f"\n\n{progress.error}\n\n" +
_t("Be aware that already processed fits were not saved"),
_message, wx.OK | wx.ICON_ERROR
errMsgLbl, wx.OK | wx.ICON_ERROR
) as dlg:
dlg.ShowModal()
return
# data is str
if action & IPortUser.PROCESS_IMPORT:
if action & IPortUser.ID_PULSE:
_message = ()
# update message
elif action & IPortUser.ID_UPDATE: # and data != self.progressDialog.message:
_message = data
if _message is not None:
self.__progress_flag, _unuse = self.progressDialog.Pulse(_message)
else:
self.closeProgressDialog()
if action & IPortUser.ID_DONE:
self._openAfterImport(data)
# data is tuple(int, str)
elif action & IPortUser.PROCESS_EXPORT:
if action & IPortUser.ID_DONE:
self.closeProgressDialog()
else:
self.__progress_flag, _unuse = self.progressDialog.Update(data[0], data[1])
elif progress.callback:
progress.callback(*progress.cbArgs)
def _openAfterImport(self, fits):
if len(fits) > 0:
@@ -988,6 +949,8 @@ class MainFrame(wx.Frame):
wx.PostEvent(self.shipBrowser, Stage3Selected(shipID=fit.shipID, back=True))
else:
fits.sort(key=lambda _fit: (_fit.ship.item.name, _fit.name))
# Show 100 fits max
fits = fits[:100]
results = []
for fit in fits:
results.append((
@@ -999,15 +962,6 @@ class MainFrame(wx.Frame):
))
wx.PostEvent(self.shipBrowser, ImportSelected(fits=results, back=True))
def closeProgressDialog(self):
# Windows apparently handles ProgressDialogs differently. We can
# simply Destroy it here, but for other platforms we must Close it
if 'wxMSW' in wx.PlatformInfo:
self.progressDialog.Destroy()
else:
self.progressDialog.EndModal(wx.ID_OK)
self.progressDialog.Close()
def importCharacter(self, event):
""" Imports character XML file from EVE API """
with wx.FileDialog(

View File

@@ -146,7 +146,7 @@ class MarketBrowser(wx.Panel):
setting = self.settings.get('marketMGSearchMode')
# We turn on all meta buttons for the duration of search/recents
if setting == 1:
if newMode in ('search', 'recent'):
if newMode in ('search', 'recent', 'charges'):
for btn in self.metaButtons:
btn.setUserSelection(True)
if newMode == 'normal':

View File

@@ -42,7 +42,7 @@ class DmgPatternNameValidator(BaseValidator):
return DmgPatternNameValidator()
def Validate(self, win):
entityEditor = win.parent
entityEditor = win.Parent.parent
textCtrl = self.GetWindow()
text = textCtrl.GetValue().strip()

View File

@@ -257,7 +257,7 @@ class PyGauge(wx.Window):
else:
w = rect.width * (float(value) / 100)
r = copy.copy(rect)
r.width = w
r.width = round(w)
dc.DrawRectangle(r)
else:
# if bar color is not set, then we use pre-defined transitions
@@ -269,7 +269,7 @@ class PyGauge(wx.Window):
else:
w = rect.width * (float(value) / 100)
r = copy.copy(rect)
r.width = w
r.width = round(w)
# determine transition range number and calculate xv (which is the
# progress between the two transition ranges)
@@ -317,7 +317,7 @@ class PyGauge(wx.Window):
gradient_color
)
if gradient_bitmap is not None:
dc.DrawBitmap(gradient_bitmap, r.left, r.top)
dc.DrawBitmap(gradient_bitmap, round(r.left), round(r.top))
# font stuff begins here
dc.SetFont(self.font)

View File

@@ -39,7 +39,7 @@ class ImplantTextValidor(BaseValidator):
return ImplantTextValidor()
def Validate(self, win):
entityEditor = win.parent
entityEditor = win.Parent.parent
textCtrl = self.GetWindow()
text = textCtrl.GetValue().strip()

View File

@@ -191,7 +191,7 @@ class ShipBrowser(wx.Panel):
"amarr", "caldari", "gallente", "minmatar",
"sisters", "ore", "concord",
"serpentis", "angel", "blood", "sansha", "guristas", "mordu",
"jove", "triglavian", "upwell", None
"deathless", "jove", "triglavian", "upwell", None
]
def raceNameKey(self, ship):

View File

@@ -2,30 +2,48 @@ import wx
import gui.mainFrame
import webbrowser
import gui.globalEvents as GE
import config
import time
from service.settings import EsiSettings
_t = wx.GetTranslation
class SsoLogin(wx.Dialog):
def __init__(self):
mainFrame = gui.mainFrame.MainFrame.getInstance()
def __init__(self, server: config.ApiServer, start_local_server=True):
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
from service.esi import Esi
super().__init__(
mainFrame, id=wx.ID_ANY, title=_t("SSO Login"), style=wx.DEFAULT_DIALOG_STYLE,
self.mainFrame, id=wx.ID_ANY, title=_t("SSO Login"), style=wx.DEFAULT_DIALOG_STYLE,
size=wx.Size(450, 240) if "wxGTK" in wx.PlatformInfo else wx.Size(400, 240))
bSizer1 = wx.BoxSizer(wx.VERTICAL)
text = wx.StaticText(self, wx.ID_ANY, _t("Copy and paste the block of text provided by pyfa.io"))
bSizer1.Add(text, 0, wx.ALL | wx.EXPAND, 10)
if start_local_server:
text = wx.StaticText(self, wx.ID_ANY, _t("Waiting for character login through EVE Single Sign-On."))
bSizer1.Add(text, 0, wx.ALL | wx.EXPAND, 10)
bSizer1.Add(wx.StaticLine(self, wx.ID_ANY), 0, wx.EXPAND, 15)
text = wx.StaticText(self, wx.ID_ANY, _t("If auto-login fails, copy and paste the token provided by pyfa.io"))
bSizer1.Add(text, 0, wx.ALL | wx.EXPAND, 10)
elif server.name == "Serenity":
text = wx.StaticText(self, wx.ID_ANY, _t("Please copy and paste the url when your authorization is completed"))
bSizer1.Add(text, 0, wx.ALL | wx.EXPAND, 10)
else:
text = wx.StaticText(self, wx.ID_ANY, _t("Please copy and paste the token provided by pyfa.io"))
bSizer1.Add(text, 0, wx.ALL | wx.EXPAND, 10)
self.ssoInfoCtrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, (-1, -1), style=wx.TE_MULTILINE)
self.ssoInfoCtrl.SetFont(wx.Font(8, wx.FONTFAMILY_TELETYPE, wx.NORMAL, wx.NORMAL))
self.ssoInfoCtrl.Layout()
self.ssoInfoCtrl.Bind(wx.EVT_TEXT, self.OnTextEnter)
bSizer1.Add(self.ssoInfoCtrl, 1, wx.LEFT | wx.RIGHT | wx.EXPAND, 10)
self.Esisettings = EsiSettings.getInstance()
bSizer3 = wx.BoxSizer(wx.VERTICAL)
bSizer3.Add(wx.StaticLine(self, wx.ID_ANY), 0, wx.BOTTOM | wx.EXPAND, 10)
@@ -34,51 +52,43 @@ class SsoLogin(wx.Dialog):
self.SetSizer(bSizer1)
self.Center()
from service.esi import Esi
self.sEsi = Esi.getInstance()
uri = self.sEsi.get_login_uri(None)
webbrowser.open(uri)
class SsoLoginServer(wx.Dialog):
def __init__(self, port):
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
super().__init__(self.mainFrame, id=wx.ID_ANY, title=_t("SSO Login"), size=(-1, -1), style=wx.DEFAULT_DIALOG_STYLE)
from service.esi import Esi
self.sEsi = Esi.getInstance()
serverAddr = self.sEsi.startServer(port)
serverAddr = self.sEsi.startServer(0) if start_local_server else None
uri = self.sEsi.get_login_uri(serverAddr)
bSizer1 = wx.BoxSizer(wx.VERTICAL)
self.mainFrame.Bind(GE.EVT_SSO_LOGIN, self.OnLogin)
self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
if server.name == "Serenity":
webbrowser.open(config.SSO_LOGOFF_SERENITY)
time.sleep(1)
text = wx.StaticText(self, wx.ID_ANY, _t("Waiting for character login through EVE Single Sign-On."))
bSizer1.Add(text, 0, wx.ALL | wx.EXPAND, 10)
bSizer3 = wx.BoxSizer(wx.VERTICAL)
bSizer3.Add(wx.StaticLine(self, wx.ID_ANY), 0, wx.BOTTOM | wx.EXPAND, 10)
bSizer3.Add(self.CreateStdDialogButtonSizer(wx.CANCEL), 0, wx.EXPAND)
bSizer1.Add(bSizer3, 0, wx.BOTTOM | wx.RIGHT | wx.LEFT | wx.EXPAND, 10)
self.SetSizer(bSizer1)
self.Fit()
self.Center()
self.okBtn = self.FindWindow(wx.ID_OK)
self.okBtn.Enable(False)
# Ensure we clean up once they hit the "OK" button
self.okBtn.Bind(wx.EVT_BUTTON, self.OnDestroy)
webbrowser.open(uri)
self.mainFrame.Bind(GE.EVT_SSO_LOGIN, self.OnLogin)
# Ensure we clean up if ESC is pressed
self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
def OnTextEnter(self, event):
t = event.String.strip()
if t == "":
self.okBtn.Enable(False)
else:
self.okBtn.Enable(True)
event.Skip()
def OnLogin(self, event):
self.EndModal(wx.ID_OK)
# This would normally happen if it was logged in via server auto-login. In this case, the modal is done, we effectively want to cancel out
self.EndModal(wx.ID_CANCEL)
event.Skip()
def OnDestroy(self, event):
# Clean up by unbinding some events and stopping the server
self.mainFrame.Unbind(GE.EVT_SSO_LOGIN, handler=self.OnLogin)
if self:
self.Unbind(wx.EVT_WINDOW_DESTROY, handler=self.OnDestroy)
self.sEsi.stopServer()
event.Skip()

View File

@@ -62,7 +62,7 @@ class TargetProfileNameValidator(BaseValidator):
return TargetProfileNameValidator()
def Validate(self, win):
entityEditor = win.parent
entityEditor = win.Parent.parent
textCtrl = self.GetWindow()
text = textCtrl.GetValue().strip()
@@ -123,13 +123,14 @@ class TargetProfileEditor(AuxiliaryFrame):
ATTRIBUTES = OrderedDict([
('maxVelocity', (_t('Maximum speed'), 'm/s')),
('signatureRadius', (_t('Signature radius\nLeave blank for infinitely big value'), 'm')),
('radius', (_t('Radius'), 'm'))])
('radius', (_t('Radius\nThe radius of the sphere that represents a ship/drone in space. Affects range calculations.'), 'm')),
('hp', (_t('Total HP\nAffects how much damage breacher pods can do. Leave blank for infinitely big value'), 'hp'))])
def __init__(self, parent):
super().__init__(
parent, id=wx.ID_ANY, title=_t("Target Profile Editor"), resizeable=True,
# Dropdown list widget is scaled to its longest content line on GTK, adapt to that
size=wx.Size(500, 240) if "wxGTK" in wx.PlatformInfo else wx.Size(350, 240))
size=wx.Size(630, 240) if "wxGTK" in wx.PlatformInfo else wx.Size(450, 240))
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.block = False
@@ -145,36 +146,29 @@ class TargetProfileEditor(AuxiliaryFrame):
contentSizer = wx.BoxSizer(wx.VERTICAL)
resistEditSizer = wx.FlexGridSizer(2, 6, 0, 2)
resistEditSizer.AddGrowableCol(0)
resistEditSizer.AddGrowableCol(5)
resistEditSizer.SetFlexibleDirection(wx.BOTH)
resistEditSizer.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_SPECIFIED)
resistEditSizer = wx.BoxSizer(wx.HORIZONTAL)
resistEditSizer.AddStretchSpacer()
defSize = wx.Size(50, -1)
defSize = wx.Size(70, -1)
for i, type_ in enumerate(self.DAMAGE_TYPES):
if i % 2:
style = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.LEFT
border = 25
else:
style = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT
border = 5
for type_ in self.DAMAGE_TYPES:
leftPad = 25 if type_ != list(self.DAMAGE_TYPES)[0] else 0
ttText = self.DAMAGE_TYPES[type_]
bmp = wx.StaticBitmap(self, wx.ID_ANY, BitmapLoader.getBitmap("%s_big" % type_, "gui"))
bmp.SetToolTip(wx.ToolTip(ttText))
resistEditSizer.Add(bmp, 0, style, border)
resistEditSizer.Add(bmp, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, leftPad)
# set text edit
editBox = FloatBox(parent=self, id=wx.ID_ANY, value=None, pos=wx.DefaultPosition, size=defSize, validator=ResistValidator())
editBox = FloatBox(parent=self, id=wx.ID_ANY, value=None, pos=wx.DefaultPosition, size=defSize)
editBox.SetToolTip(wx.ToolTip(ttText))
self.Bind(event=wx.EVT_TEXT, handler=self.OnFieldChanged, source=editBox)
setattr(self, '{}Edit'.format(type_), editBox)
resistEditSizer.Add(editBox, 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5)
resistEditSizer.Add(editBox, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
unit = wx.StaticText(self, wx.ID_ANY, "%", wx.DefaultPosition, wx.DefaultSize, 0)
unit.SetToolTip(wx.ToolTip(ttText))
resistEditSizer.Add(unit, 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5)
contentSizer.Add(resistEditSizer, 0, wx.EXPAND | wx.ALL, 5)
resistEditSizer.AddStretchSpacer()
contentSizer.Add(resistEditSizer, 1, wx.EXPAND | wx.ALL, 5)
miscAttrSizer = wx.BoxSizer(wx.HORIZONTAL)
miscAttrSizer.AddStretchSpacer()

View File

@@ -66,8 +66,8 @@ class UpdateDialog(wx.Dialog):
self.browser.Bind(wx.html2.EVT_WEBVIEW_NEWWINDOW, self.OnNewWindow)
link_patterns = [
(re.compile("#(\d+)", re.I), r"https://github.com/pyfa-org/Pyfa/issues/\1"),
(re.compile("@(\w+)", re.I), r"https://github.com/\1")
(re.compile(r"#(\d+)", re.I), r"https://github.com/pyfa-org/Pyfa/issues/\1"),
(re.compile(r"@(\w+)", re.I), r"https://github.com/\1")
]
markdowner = markdown2.Markdown(

View File

@@ -80,7 +80,7 @@ class LoadAnimation(wx.Window):
bh = rect.height
y = 0
dc.DrawRectangle(x, y, barWidth, bh)
dc.DrawRectangle(round(x), round(y), round(barWidth), round(bh))
x += barWidth
textColor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)

View File

@@ -12,7 +12,7 @@ def Brighten(color, factor):
b += (255 - b) * factor
g += (255 - g) * factor
return wx.Colour(r, g, b, a)
return wx.Colour(round(r), round(g), round(b), round(a))
def Darken(color, factor):
@@ -30,7 +30,7 @@ def Darken(color, factor):
b = min(max(b, 0), 255)
g = min(max(g, 0), 255)
return wx.Colour(r, g, b, a)
return wx.Colour(round(r), round(g), round(b), round(a))
def _getBrightness(color):
@@ -70,4 +70,4 @@ def CalculateTransition(s_color, e_color, delta):
tG = sG + (eG - sG) * delta
tB = sB + (eB - sB) * delta
return wx.Colour(tR, tG, tB, (sA + eA) / 2)
return wx.Colour(round(tR), round(tG), round(tB), round((sA + eA) / 2))

12
gui/utils/dark.py Normal file
View File

@@ -0,0 +1,12 @@
import wx
def isDark():
if 'wxMSW' in wx.PlatformInfo:
return False
try:
return wx.SystemSettings.GetAppearance().IsDark()
except (KeyboardInterrupt, SystemExit):
raise
except:
return False

View File

@@ -21,7 +21,7 @@ def RenderGradientBar(windowColor, width, height, sFactor, eFactor, mFactor=None
def DrawFilledBitmap(width, height, color):
canvas = wx.Bitmap(width, height)
canvas = wx.Bitmap(round(width), round(height))
mdc = wx.MemoryDC()
mdc.SelectObject(canvas)
@@ -37,20 +37,20 @@ def DrawFilledBitmap(width, height, color):
def DrawGradientBar(width, height, gStart, gEnd, gMid=None, fillRatio=4):
if width == 0 or height == 0:
return None
canvas = wx.Bitmap(width, height)
canvas = wx.Bitmap(round(width), round(height))
mdc = wx.MemoryDC()
mdc.SelectObject(canvas)
r = wx.Rect(0, 0, width, height)
r.SetHeight(height / fillRatio)
r.SetHeight(round(height / fillRatio))
if gMid is None:
gMid = gStart
mdc.GradientFillLinear(r, gStart, gMid, wx.SOUTH)
r.SetTop(r.GetHeight())
r.SetHeight(height * (fillRatio - 1) / fillRatio + (1 if height % fillRatio != 0 else 0))
r.SetHeight(round(height * (fillRatio - 1) / fillRatio + (1 if height % fillRatio != 0 else 0)))
mdc.GradientFillLinear(r, gMid, gEnd, wx.SOUTH)

View File

@@ -26,20 +26,20 @@ class exportHtml:
def __init__(self):
self.thread = exportHtmlThread()
def refreshFittingHtml(self, force=False, callback=False):
def refreshFittingHtml(self, force=False, progress=None):
settings = HTMLExportSettings.getInstance()
if force or settings.getEnabled():
self.thread.stop()
self.thread = exportHtmlThread(callback)
self.thread = exportHtmlThread(progress)
self.thread.start()
class exportHtmlThread(threading.Thread):
def __init__(self, callback=False):
def __init__(self, progress=False):
threading.Thread.__init__(self)
self.name = "HTMLExport"
self.callback = callback
self.progress = progress
self.stopRunning = False
def stop(self):
@@ -72,11 +72,13 @@ class exportHtmlThread(threading.Thread):
pass
except (KeyboardInterrupt, SystemExit):
raise
except Exception as ex:
pass
if self.callback:
wx.CallAfter(self.callback, -1)
except Exception as e:
if self.progress:
self.progress.error = f'{e}'
finally:
if self.progress:
self.progress.current += 1
self.progress.workerWorking = False
def generateFullHTML(self, sMkt, sFit, dnaUrl):
""" Generate the complete HTML with styling and javascript """
@@ -171,13 +173,13 @@ class exportHtmlThread(threading.Thread):
</head>
<body>
<div id="canvas" data-role="page">
<div style="text-align: center;"><strong>Last updated:</strong> %s <small>(<span class="timer"></span>)</small></div>
<div data-role="header">
<h1>Pyfa fits</h1>
<h1>Pyfa fits by Group</h1>
</div>
<div data-role="content">
<div style="text-align: center;"><strong>Last updated:</strong> %s <small>(<span class="timer"></span>)</small></div>
""" % (time.time(), dnaUrl, localDate)
HTML += ' <ul data-role="listview" class="ui-listview-outer" data-inset="true" data-filter="true">\n'
categoryList = list(sMkt.getShipRoot())
categoryList.sort(key=lambda _ship: _ship.name)
@@ -214,7 +216,9 @@ class exportHtmlThread(threading.Thread):
eftFit = Port.exportEft(getFit(fit[0]), options={
PortEftOptions.IMPLANTS: True,
PortEftOptions.MUTATIONS: True,
PortEftOptions.LOADED_CHARGES: True})
PortEftOptions.LOADED_CHARGES: True,
PortEftOptions.BOOSTERS: True,
PortEftOptions.CARGO: True})
HTMLfit = (
' <li data-role="collapsible" data-iconpos="right" data-shadow="false" '
@@ -234,8 +238,8 @@ class exportHtmlThread(threading.Thread):
pyfalog.warning("Failed to export line")
continue
finally:
if self.callback:
wx.CallAfter(self.callback, count)
if self.progress:
self.progress.current = count
count += 1
HTMLgroup += HTMLship + (' </ul>\n'
' </li>\n')
@@ -244,7 +248,7 @@ class exportHtmlThread(threading.Thread):
# Market group header
HTML += (
' <li data-role="collapsible" data-iconpos="right" data-shadow="false" data-corners="false">\n'
' <h2>' + group.groupName + ' <span class="ui-li-count">' + str(groupFits) + '</span></h2>\n'
' <h2>' + group.name + ' <span class="ui-li-count">' + str(groupFits) + '</span></h2>\n'
' <ul data-role="listview" data-shadow="false" data-inset="true" data-corners="false">\n' +
HTMLgroup +
' </ul>\n'
@@ -254,6 +258,68 @@ class exportHtmlThread(threading.Thread):
HTML += """
</ul>
</div>
<div data-role="header">
<h1>Pyfa fits by Name</h1>
</div>
<div data-role="content">
"""
HTML += ' <ul data-role="listview" class="ui-listview-outer" data-inset="true" data-filter="true">\n'
categoryList = list(sMkt.getShipRoot())
categoryList.sort(key=lambda _ship: _ship.name)
count = 0
for group in categoryList:
# init market group string to give ships something to attach to
HTMLgroup = ''
ships = list(sMkt.getShipList(group.ID))
ships.sort(key=lambda _ship: _ship.name)
# Keep track of how many ships per group
groupFits = 0
for ship in ships:
fits = sFit.getFitsWithShip(ship.ID)
if len(fits) > 0:
groupFits += len(fits)
for fit in fits:
if self.stopRunning:
return
try:
eftFit = Port.exportEft(getFit(fit[0]), options={
PortEftOptions.IMPLANTS: True,
PortEftOptions.MUTATIONS: True,
PortEftOptions.LOADED_CHARGES: True,
PortEftOptions.BOOSTERS: True,
PortEftOptions.CARGO: True})
HTMLfit = (
' <li data-role="collapsible" data-iconpos="right" data-shadow="false" '
'data-corners="false">\n'
' <h2>' + ship.name + " - " + fit[1] + '</h2>\n'
' <ul data-role="listview" data-shadow="false" data-inset="true" '
'data-corners="false">\n'
)
HTMLfit += ' <li><pre>' + eftFit + '\n </pre></li>\n'
HTMLfit += ' </ul>\n </li>\n'
HTML += HTMLfit
except (KeyboardInterrupt, SystemExit):
raise
except:
pyfalog.warning("Failed to export line")
continue
finally:
if self.progress:
self.progress.current = count
count += 1
HTML += """
</ul>
</div>
</div>
</body>
</html>"""
@@ -291,7 +357,7 @@ class exportHtmlThread(threading.Thread):
pyfalog.error("Failed to export line")
continue
finally:
if self.callback:
wx.CallAfter(self.callback, count)
if self.progress:
self.progress.current = count
count += 1
return HTML

32
gui/utils/gdi.py Normal file
View File

@@ -0,0 +1,32 @@
import gc
from ctypes import *
from collections import defaultdict
import os
def gdiReport(desc=''):
PH = windll.kernel32.OpenProcess(0x400, 0, os.getpid())
numGdi = windll.user32.GetGuiResources(PH, 0)
windll.kernel32.CloseHandle(PH)
print (f'{desc}, {numGdi}')
last = None
def output_memory():
global last
d = defaultdict(int)
for o in gc.get_objects():
name = type(o).__name__
if name == 'Bitmap':
del o
d[name] += 1
items = d.items()
items = sorted(items,key=lambda x:x[1])
print('------')
for key, value in items:
if last is not None:
if value -last[key] !=0:
print(f'{key} {value - last[key]}, {value}')
else:
print( key, value)
last = d

View File

@@ -58,6 +58,9 @@ class InputValidator(metaclass=ABCMeta):
class FloatBox(wx.TextCtrl):
def __init__(self, parent, value, id=wx.ID_ANY, style=0, validator=None, **kwargs):
# Workaround for #2591
if 'wxMac' in wx.PlatformInfo and 'size' not in kwargs:
kwargs['size'] = wx.Size(97, 26)
super().__init__(parent=parent, id=id, style=style, **kwargs)
self.Bind(wx.EVT_TEXT, self.OnText)
self._storedValue = ''
@@ -93,7 +96,7 @@ class FloatBox(wx.TextCtrl):
if currentValue == self._storedValue:
event.Skip()
return
if currentValue == '' or re.match('^\d*\.?\d*$', currentValue):
if currentValue == '' or re.match(r'^\d*\.?\d*$', currentValue):
self._storedValue = currentValue
self.updateColor()
event.Skip()
@@ -107,6 +110,9 @@ class FloatBox(wx.TextCtrl):
class FloatRangeBox(wx.TextCtrl):
def __init__(self, parent, value, id=wx.ID_ANY, style=0, **kwargs):
# Workaround for #2591
if 'wxMac' in wx.PlatformInfo and 'size' not in kwargs:
kwargs['size'] = wx.Size(97, 26)
super().__init__(parent=parent, id=id, style=style, **kwargs)
self.Bind(wx.EVT_TEXT, self.OnText)
self._storedValue = ''
@@ -125,7 +131,7 @@ class FloatRangeBox(wx.TextCtrl):
if currentValue == self._storedValue:
event.Skip()
return
if currentValue == '' or re.match('^\d*\.?\d*-?\d*\.?\d*$', currentValue):
if currentValue == '' or re.match(r'^\d*\.?\d*-?\d*\.?\d*$', currentValue):
self._storedValue = currentValue
event.Skip()
else:

View File

@@ -0,0 +1,19 @@
class ProgressHelper:
def __init__(self, message, maximum=None, callback=None):
self.message = message
self.current = 0
self.maximum = maximum
self.workerWorking = True
self.dlgWorking = True
self.error = None
self.callback = callback
self.cbArgs = []
@property
def working(self):
return self.workerWorking and self.dlgWorking and not self.error
@property
def userCancelled(self):
return not self.dlgWorking