217 lines
7.1 KiB
Python
217 lines
7.1 KiB
Python
# =============================================================================
|
|
# Copyright (C) 2025
|
|
#
|
|
# This file is part of pyfa.
|
|
#
|
|
# pyfa is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# pyfa is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
|
# =============================================================================
|
|
|
|
|
|
# noinspection PyPackageRequirements
|
|
import wx
|
|
import re
|
|
|
|
from service.fit import Fit as svcFit
|
|
from service.port.eft import exportEft
|
|
from service.const import PortEftOptions
|
|
|
|
_t = wx.GetTranslation
|
|
|
|
# Regex for parsing items: itemName x? quantity?, ,? chargeName?
|
|
ITEM_REGEX = re.compile(
|
|
r"^(?P<itemName>[-\'\w\s]+?)x?\s*(?P<quantity>\d+)?\s*(?:,\s*(?P<chargeName>[-\'\w\s]+))?$"
|
|
)
|
|
|
|
|
|
class FitDiffFrame(wx.Frame):
|
|
"""A frame to display differences between two fits."""
|
|
|
|
def __init__(self, parent, fitID):
|
|
super().__init__(
|
|
parent,
|
|
title=_t("Fit Diff"),
|
|
style=wx.DEFAULT_FRAME_STYLE | wx.RESIZE_BORDER,
|
|
size=(1000, 600)
|
|
)
|
|
self.parent = parent
|
|
self.fitID = fitID
|
|
self.sFit = svcFit.getInstance()
|
|
|
|
# EFT export options (same as CTRL-C)
|
|
self.eftOptions = {
|
|
PortEftOptions.LOADED_CHARGES: True,
|
|
PortEftOptions.MUTATIONS: True,
|
|
PortEftOptions.IMPLANTS: True,
|
|
PortEftOptions.BOOSTERS: True,
|
|
PortEftOptions.CARGO: True,
|
|
}
|
|
|
|
self.initUI()
|
|
self.Centre()
|
|
self.Show()
|
|
|
|
def initUI(self):
|
|
panel = wx.Panel(self)
|
|
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
|
|
|
# Instructions and flip button at the top
|
|
topSizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
instructions = wx.StaticText(
|
|
panel,
|
|
label=_t("Paste fits in EFT format to compare")
|
|
)
|
|
topSizer.Add(instructions, 1, wx.ALL | wx.EXPAND, 5)
|
|
|
|
flipButton = wx.Button(panel, label=_t("Flip"))
|
|
flipButton.Bind(wx.EVT_BUTTON, self.onFlip)
|
|
topSizer.Add(flipButton, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
|
|
|
|
mainSizer.Add(topSizer, 0, wx.EXPAND)
|
|
|
|
# Three panes: Fit 1 | Diff | Fit 2
|
|
panesSizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
|
|
# Pane 1: Fit 1 (editable)
|
|
fit1Box = wx.StaticBox(panel, label=_t("Fit 1"))
|
|
fit1Sizer = wx.StaticBoxSizer(fit1Box, wx.VERTICAL)
|
|
self.fit1Text = wx.TextCtrl(
|
|
panel,
|
|
style=wx.TE_MULTILINE | wx.TE_DONTWRAP
|
|
)
|
|
fit1Sizer.Add(self.fit1Text, 1, wx.EXPAND)
|
|
panesSizer.Add(fit1Sizer, 1, wx.ALL | wx.EXPAND, 5)
|
|
|
|
# Bind text changed event to update diff
|
|
self.fit1Text.Bind(wx.EVT_TEXT, self.onFitChanged)
|
|
|
|
# Pane 2: Diff (simple text format)
|
|
diffBox = wx.StaticBox(panel, label=_t("Differences"))
|
|
diffSizer = wx.StaticBoxSizer(diffBox, wx.VERTICAL)
|
|
self.diffText = wx.TextCtrl(
|
|
panel,
|
|
style=wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_DONTWRAP
|
|
)
|
|
diffSizer.Add(self.diffText, 1, wx.EXPAND)
|
|
panesSizer.Add(diffSizer, 1, wx.ALL | wx.EXPAND, 5)
|
|
|
|
# Pane 3: Fit 2 (user input)
|
|
fit2Box = wx.StaticBox(panel, label=_t("Fit 2"))
|
|
fit2Sizer = wx.StaticBoxSizer(fit2Box, wx.VERTICAL)
|
|
self.fit2Text = wx.TextCtrl(
|
|
panel,
|
|
style=wx.TE_MULTILINE | wx.TE_DONTWRAP
|
|
)
|
|
fit2Sizer.Add(self.fit2Text, 1, wx.EXPAND)
|
|
|
|
# Bind text changed event to update diff
|
|
self.fit2Text.Bind(wx.EVT_TEXT, self.onFitChanged)
|
|
|
|
panesSizer.Add(fit2Sizer, 1, wx.ALL | wx.EXPAND, 5)
|
|
|
|
mainSizer.Add(panesSizer, 1, wx.EXPAND | wx.ALL, 5)
|
|
|
|
panel.SetSizer(mainSizer)
|
|
|
|
# Load current fit into pane 1
|
|
self.loadFit1()
|
|
|
|
def loadFit1(self):
|
|
"""Load the current fit into pane 1 as EFT format."""
|
|
fit = self.sFit.getFit(self.fitID)
|
|
if fit:
|
|
eftText = exportEft(fit, self.eftOptions, callback=None)
|
|
self.fit1Text.SetValue(eftText)
|
|
|
|
def onFitChanged(self, event):
|
|
"""Handle text change in either fit pane - update diff."""
|
|
self.updateDiff()
|
|
event.Skip()
|
|
|
|
def onFlip(self, event):
|
|
"""Swap Fit 1 and Fit 2."""
|
|
fit1Value = self.fit1Text.GetValue()
|
|
fit2Value = self.fit2Text.GetValue()
|
|
self.fit1Text.SetValue(fit2Value)
|
|
self.fit2Text.SetValue(fit1Value)
|
|
self.updateDiff()
|
|
event.Skip()
|
|
|
|
def updateDiff(self):
|
|
"""Calculate and display the differences between the two fits."""
|
|
self.diffText.Clear()
|
|
|
|
fit1Text = self.fit1Text.GetValue().strip()
|
|
fit2Text = self.fit2Text.GetValue().strip()
|
|
|
|
if not fit1Text or not fit2Text:
|
|
return
|
|
|
|
# Parse both fits
|
|
fit1 = self.parsePastedFit(fit1Text)
|
|
fit2 = self.parsePastedFit(fit2Text)
|
|
|
|
if fit1 is None:
|
|
self.diffText.SetValue(_t("Error: Fit 1 has invalid EFT format"))
|
|
return
|
|
if fit2 is None:
|
|
self.diffText.SetValue(_t("Error: Fit 2 has invalid EFT format"))
|
|
return
|
|
|
|
# Calculate differences and format as simple text list
|
|
diffLines = self.calculateDiff(fit1, fit2)
|
|
self.diffText.SetValue('\n'.join(diffLines))
|
|
|
|
def parsePastedFit(self, text):
|
|
"""Parse pasted EFT text into a map of item name to count."""
|
|
items = {}
|
|
for line in text.splitlines():
|
|
line = line.strip()
|
|
if not line or line.startswith("["):
|
|
continue
|
|
match = ITEM_REGEX.match(line)
|
|
if match:
|
|
item_name = match.group("itemName").strip()
|
|
quantity = match.group("quantity")
|
|
count = int(quantity) if quantity else 1
|
|
if item_name not in items:
|
|
items[item_name] = 0
|
|
items[item_name] += count
|
|
return items
|
|
|
|
def calculateDiff(self, fit1_items, fit2_items):
|
|
"""Calculate items needed to transform fit1 into fit2.
|
|
|
|
Returns a list of strings showing additions and extra items.
|
|
"""
|
|
diffLines = []
|
|
|
|
all_items = set(fit1_items.keys()) | set(fit2_items.keys())
|
|
additions = []
|
|
extras = []
|
|
|
|
for item in sorted(all_items):
|
|
count1 = fit1_items.get(item, 0)
|
|
count2 = fit2_items.get(item, 0)
|
|
if count2 > count1:
|
|
additions.append(f"{item} x{count2 - count1}")
|
|
elif count1 > count2:
|
|
extras.append(f"{item} x-{count1 - count2}")
|
|
|
|
diffLines.extend(additions)
|
|
if additions and extras:
|
|
diffLines.extend(["", ""])
|
|
diffLines.extend(extras)
|
|
|
|
return diffLines
|