diff --git a/gui/main_gui.ui b/gui/main_gui.ui index 13203cf..72eb4aa 100644 --- a/gui/main_gui.ui +++ b/gui/main_gui.ui @@ -58,25 +58,6 @@ - - - - £0000000000 - - - £ - - - 11 - - - true - - - 1 - - - @@ -138,6 +119,28 @@ + + + + true + + + QAbstractSpinBox::ButtonSymbols::NoButtons + + + £ + + + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + + £ + + + 999999999.990000009536743 + + + diff --git a/gui/platform_edit.ui b/gui/platform_edit.ui index ba3416c..a3a70ad 100644 --- a/gui/platform_edit.ui +++ b/gui/platform_edit.ui @@ -6,8 +6,8 @@ 0 0 - 484 - 290 + 481 + 305 @@ -19,7 +19,7 @@ 10 10 461 - 226 + 251 @@ -36,32 +36,11 @@ 6 - 15 - - 10 - - - - Share platform fee cap (£/mth) - - - - - - - Share dealing fee discount (£) - - - - - - - Share dealing fee (£) - - - + + 5 + @@ -69,14 +48,12 @@ - - - - - - - - + + + + Share dealing discount amount + + @@ -84,26 +61,10 @@ - Share platform fee (%) + Share platform fee* - - - - - - - - - - Fund dealing fee (£) - - - - - - @@ -111,14 +72,113 @@ + + + + Share dealing fee* + + + + + + + Fund dealing fee* + + + + + + + Share platform fee cap/mth + + + + + + + QAbstractSpinBox::ButtonSymbols::NoButtons + + + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + + £ + + + + + + + QAbstractSpinBox::ButtonSymbols::NoButtons + + + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + + £ + + + + + + + QAbstractSpinBox::ButtonSymbols::NoButtons + + + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + + £ + + + + + + + QAbstractSpinBox::ButtonSymbols::NoButtons + + + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + + % + + + + + + + QAbstractSpinBox::ButtonSymbols::NoButtons + + + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + + £ + + + + + + + QAbstractSpinBox::ButtonSymbols::NoButtons + + + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + + + + false + - 290 - 260 - 191 + 330 + 270 + 141 24 @@ -126,6 +186,19 @@ Save + + + + 20 + 270 + 191 + 21 + + + + * Indicates required field + + plat_name_box @@ -133,7 +206,6 @@ share_plat_fee_box share_plat_max_fee_box share_deal_fee_box - share_deal_reduce_trades_box share_deal_reduce_amount_box save_but diff --git a/src/main.py b/src/main.py index 9aa47e9..14c9b4d 100644 --- a/src/main.py +++ b/src/main.py @@ -7,6 +7,14 @@ import platform_edit app = QApplication(sys.argv) # Show platform edit window first, before main win -window = platform_edit.PlatformEdit() +# When debugging, can be useful to autofill values to save time +if len(sys.argv) > 1: + if sys.argv[1] == "--DEBUG_AUTOFILL": + window = platform_edit.PlatformEdit(True) + else: + window = platform_edit.PlatformEdit(False) +else: + window = platform_edit.PlatformEdit(False) + window.show() app.exec() diff --git a/src/main_window.py b/src/main_window.py index 892cb1a..5d458b3 100644 --- a/src/main_window.py +++ b/src/main_window.py @@ -1,3 +1,4 @@ +from PyQt6.QtGui import QIntValidator from PyQt6.QtWidgets import QMainWindow from PyQt6 import uic @@ -8,9 +9,11 @@ class SIPPCompare(QMainWindow): # Receive instance of PlatformEdit() as parameter def __init__(self, plat_edit_win): super().__init__() + # Import Qt Designer UI XML file uic.loadUi("gui/main_gui.ui", self) # Initialise class variables + # Inputs self.fund_plat_fee = 0.0 self.plat_name = "" self.fund_deal_fee = 0.0 @@ -20,6 +23,7 @@ class SIPPCompare(QMainWindow): self.share_deal_reduce_trades = 0.0 self.share_deal_reduce_amount = 0.0 + # Results self.fund_plat_fees = 0.0 self.fund_deal_fees = 0.0 self.share_plat_fees = 0.0 @@ -31,16 +35,22 @@ class SIPPCompare(QMainWindow): # Handle events self.calc_but.clicked.connect(self.calculate_fees) + # Menu bar entry (File -> Edit Platforms) self.actionEdit_Platforms.triggered.connect(self.show_platform_edit) + # Update percentage mix label when slider moved self.mix_slider.valueChanged.connect(self.update_slider_lab) + # Set validators + self.share_trades_combo.setValidator(QIntValidator(0, 999)) + self.fund_trades_combo.setValidator(QIntValidator(0, 99)) + # Display slider position as mix between two nums (funds/shares) def update_slider_lab(self): slider_val = self.mix_slider.value() mix_lab_str = f"Investment mix (funds {slider_val}% / shares {100 - slider_val}%)" self.mix_lab.setText(mix_lab_str) - # Get local variables from user input + # Get variables from platform editor input fields def init_variables(self): self.plat_name = self.platform_win.get_plat_name() self.fund_plat_fee = self.platform_win.get_fund_plat_fee() @@ -52,15 +62,17 @@ class SIPPCompare(QMainWindow): self.share_deal_reduce_amount = self.platform_win.get_share_deal_reduce_amount() # Calculate fees + # TODO: Error checking on combo boxes def calculate_fees(self): self.init_variables() + # Set to zero each time to avoid persistence self.fund_plat_fees = 0 - value_num = float(self.value_input.text()[1:]) # Filter out '£' symbol + value_num = float(self.value_input.value()) + # Funds/shares mix slider_val = self.mix_slider.value() funds_value = (slider_val / 100) * value_num fund_trades_num = int(self.fund_trades_combo.currentText()) self.fund_deal_fees = fund_trades_num * self.fund_deal_fee - remaining = funds_value for i in range(1, len(self.fund_plat_fee[0])): band = self.fund_plat_fee[0][i] @@ -68,11 +80,11 @@ class SIPPCompare(QMainWindow): fee = self.fund_plat_fee[1][i] gap = (band - prev_band) - if remaining > gap: + if funds_value > gap: self.fund_plat_fees += gap * (fee / 100) - remaining -= gap + funds_value -= gap else: - self.fund_plat_fees += remaining * (fee / 100) + self.fund_plat_fees += funds_value * (fee / 100) break shares_value = (1 - (slider_val / 100)) * value_num @@ -93,7 +105,9 @@ class SIPPCompare(QMainWindow): def show_output_win(self): # Refresh the results when new fees are calculated self.output_win.display_output(self.fund_plat_fees, self.fund_deal_fees, - self.share_plat_fees, self.share_deal_fees, self.plat_name) + self.share_plat_fees, self.share_deal_fees, + self.plat_name + ) self.output_win.show() # Show the platform editor window (currently run-time only) diff --git a/src/output_window.py b/src/output_window.py index 0c7dbda..2e8a2b5 100644 --- a/src/output_window.py +++ b/src/output_window.py @@ -8,6 +8,7 @@ import os class OutputWindow(QWidget): def __init__(self): super().__init__() + # Import Qt Designer UI XML file uic.loadUi("gui/output_window.ui", self) # Initialise class variables diff --git a/src/platform_edit.py b/src/platform_edit.py index 71c8664..2e51309 100644 --- a/src/platform_edit.py +++ b/src/platform_edit.py @@ -1,3 +1,5 @@ +from PyQt6.QtCore import QRegularExpression, QEvent, QObject, QTimer +from PyQt6.QtGui import QRegularExpressionValidator from PyQt6.QtWidgets import QWidget from PyQt6 import uic @@ -5,8 +7,9 @@ import main_window class PlatformEdit(QWidget): - def __init__(self): + def __init__(self, autofill: bool): super().__init__() + # Import Qt Designer UI XML file uic.loadUi("gui/platform_edit.ui", self) # Initialise class variables @@ -22,28 +25,83 @@ class PlatformEdit(QWidget): self.share_deal_fee = 0.0 self.share_deal_reduce_trades = 0.0 self.share_deal_reduce_amount = 0.0 + # Debugging feature: set with "--DEBUG_AUTOFILL" cmd argument + self.autofill = autofill # Create main window object, passing this instance as param self.main_win = main_window.SIPPCompare(self) # Handle events - # NOTE: Signal defined in Qt designer to close window when clicked + # NOTE: Signal defined in UI file to close window when save button clicked self.save_but.clicked.connect(self.init_variables) + self.fund_deal_fee_box.valueChanged.connect(self.check_valid) + self.share_plat_fee_box.valueChanged.connect(self.check_valid) + self.share_deal_fee_box.valueChanged.connect(self.check_valid) - # Get fee structure variables from user input + # Install event filter on input boxes in order to select all text on focus + self.fund_deal_fee_box.installEventFilter(self) + self.share_plat_fee_box.installEventFilter(self) + self.share_plat_max_fee_box.installEventFilter(self) + self.share_deal_fee_box.installEventFilter(self) + self.share_deal_reduce_trades_box.installEventFilter(self) + self.share_deal_reduce_amount_box.installEventFilter(self) + + # Set validators + # Regex accepts any characters that match [a-Z], [0-9] or _ + self.plat_name_box.setValidator( + QRegularExpressionValidator(QRegularExpression("\\w*")) + ) + + # Get fee structure variables from user input when "Save" clicked def init_variables(self): - self.plat_name = self.plat_name_box.text() - self.fund_deal_fee = float(self.fund_deal_fee_box.text()) - self.share_plat_fee = float(self.share_plat_fee_box.text()) / 100 - self.share_plat_max_fee = float(self.share_plat_max_fee_box.text()) - self.share_deal_fee = float(self.share_deal_fee_box.text()) - self.share_deal_reduce_trades = float(self.share_deal_reduce_trades_box.text()) - self.share_deal_reduce_amount = float(self.share_deal_reduce_amount_box.text()) + # If debugging, save time by hardcoding + if self.autofill: + self.plat_name = "AJBell" + self.fund_deal_fee = 1.50 + self.share_plat_fee = 0.0025 + self.share_plat_max_fee = 3.50 + self.share_deal_fee = 5.00 + self.share_deal_reduce_trades = 10 + self.share_deal_reduce_amount = 3.50 + else: + self.plat_name = self.plat_name_box.text() + self.fund_deal_fee = float(self.fund_deal_fee_box.value()) + self.share_plat_fee = float(self.share_plat_fee_box.value()) / 100 + self.share_plat_max_fee = float(self.share_plat_max_fee_box.value()) + self.share_deal_fee = float(self.share_deal_fee_box.value()) + self.share_deal_reduce_trades = float(self.share_deal_reduce_trades_box.value()) + self.share_deal_reduce_amount = float(self.share_deal_reduce_amount_box.value()) # Once user input is received show main window self.main_win.show() - # Getter functions + # When focus is given to an input box, select all text in it (easier to edit) + def eventFilter(self, obj: QObject, event: QEvent): + if event.type() == QEvent.Type.FocusIn: + # Alternative condition for % suffix - currently unused + #if obj.value() == 0 or obj == self.share_plat_fee_box: + QTimer.singleShot(0, obj.selectAll) + return False + + # Check if all required fields have valid (non-zero) input + # TODO: Find a better way of doing this if possible + def check_valid(self): + values = [self.fund_deal_fee_box.value(), + self.share_plat_fee_box.value(), + self.share_deal_fee_box.value() + ] + valid = True + + for value in values: + if value == 0: + valid = False + + if valid: + self.save_but.setEnabled(True) + else: + self.save_but.setEnabled(False) + + # Getter functions (is this necessary? maybe directly reading class vars would be best...) def get_plat_name(self): return self.plat_name