diff --git a/gui/platform_edit.ui b/gui/platform_edit.ui
index 0cc4a1f..ddf9a0e 100644
--- a/gui/platform_edit.ui
+++ b/gui/platform_edit.ui
@@ -6,20 +6,20 @@
0
0
- 480
- 305
+ 498
+ 303
- 480
- 305
+ 498
+ 303
- 480
- 305
+ 498
+ 303
@@ -28,10 +28,10 @@
- 10
- 10
+ 20
+ 20
461
- 251
+ 243
@@ -53,99 +53,166 @@
5
- -
-
-
- Platform name
+
-
+
+
+ true
+
+
+
+ -
+
+
+ false
+
+
+ true
+
+
+
+ -
+
+
+ false
+
+
+ QAbstractSpinBox::ButtonSymbols::NoButtons
+
+
+ QAbstractSpinBox::CorrectionMode::CorrectToNearestValue
+
+
+ £
+
+
+ 999.000000000000000
+
+
+
+ -
+
+
+ false
+
+
+ true
+
+
+
+ -
+
+
+ QAbstractSpinBox::ButtonSymbols::NoButtons
+
+
+ QAbstractSpinBox::CorrectionMode::CorrectToNearestValue
+
+
+ £
+
+
+ 999.000000000000000
-
-
-
- Share dealing discount amount
-
-
-
- -
-
-
- -
-
-
- Share platform fee*
-
-
-
- -
Share dealing discount # of trades
+ -
+
+
-
+
+
+ Share platform monthly fee cap
+
+
+
+ -
+
+
+ QAbstractSpinBox::ButtonSymbols::NoButtons
+
+
+ QAbstractSpinBox::CorrectionMode::CorrectToNearestValue
+
+
+ £
+
+
+ 999.000000000000000
+
+
+
+ -
Share dealing fee*
- -
+
-
+
+
+ false
+
+
+ QAbstractSpinBox::ButtonSymbols::NoButtons
+
+
+ QAbstractSpinBox::CorrectionMode::CorrectToNearestValue
+
+
+ 999
+
+
+
+ -
+
+
+ -
+
+
+ false
+
+
+ true
+
+
+
+ -
Fund dealing fee*
- -
-
+
-
+
+
+ -
+
+
+ -
+
- Share platform fee cap/mth
+ Share dealing discount amount
- -
-
-
- QAbstractSpinBox::ButtonSymbols::NoButtons
-
-
- QAbstractSpinBox::CorrectionMode::CorrectToNearestValue
-
-
- £
-
-
-
- -
-
-
- QAbstractSpinBox::ButtonSymbols::NoButtons
-
-
- QAbstractSpinBox::CorrectionMode::CorrectToNearestValue
-
-
- £
+
-
+
+
+ Share platform fee*
-
-
-
- QAbstractSpinBox::ButtonSymbols::NoButtons
-
-
- QAbstractSpinBox::CorrectionMode::CorrectToNearestValue
-
-
- £
-
-
-
- -
-
+
QAbstractSpinBox::ButtonSymbols::NoButtons
@@ -155,10 +222,23 @@
%
+
+ 99.000000000000000
+
- -
-
+
-
+
+
+ Platform name
+
+
+
+ -
+
+
+ false
+
QAbstractSpinBox::ButtonSymbols::NoButtons
@@ -168,15 +248,8 @@
£
-
-
- -
-
-
- QAbstractSpinBox::ButtonSymbols::NoButtons
-
-
- QAbstractSpinBox::CorrectionMode::CorrectToNearestValue
+
+ 999.000000000000000
@@ -188,7 +261,7 @@
- 330
+ 350
270
141
24
@@ -198,11 +271,11 @@
Save
-
+
- 20
- 270
+ 10
+ 280
191
21
@@ -211,7 +284,35 @@
* Indicates required field
+
+
+
+ 437
+ 10
+ 61
+ 16
+
+
+
+ Active?
+
+
+ Qt::AlignmentFlag::AlignCenter
+
+
+
+
+ FastEditQDoubleSpinBox
+ QDoubleSpinBox
+
+
+
+ FastEditQSpinBox
+ QSpinBox
+
+
+
plat_name_box
fund_deal_fee_box
@@ -221,6 +322,13 @@
share_deal_reduce_trades_box
share_deal_reduce_amount_box
save_but
+ plat_name_check
+ fund_deal_fee_check
+ share_plat_fee_check
+ share_plat_max_fee_check
+ share_deal_fee_check
+ share_deal_reduce_trades_check
+ share_deal_reduce_amount_check
diff --git a/src/main_window.py b/src/main_window.py
index 14060e3..093e511 100644
--- a/src/main_window.py
+++ b/src/main_window.py
@@ -14,6 +14,7 @@ class SIPPCompare(QMainWindow):
# Initialise class variables
# Inputs
+ self.optional_boxes = []
self.fund_plat_fee = 0.0
self.plat_name = ""
self.fund_deal_fee = 0.0
@@ -39,7 +40,7 @@ class SIPPCompare(QMainWindow):
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)
- #self.value_input.valueChanged.connect(self.check_valid)
+ self.value_input.valueChanged.connect(self.check_valid)
self.share_trades_combo.currentTextChanged.connect(self.check_valid)
self.fund_trades_combo.currentTextChanged.connect(self.check_valid)
@@ -55,24 +56,42 @@ class SIPPCompare(QMainWindow):
def check_valid(self):
if self.share_trades_combo.currentText() != "" \
- and self.fund_trades_combo.currentText() != "":
+ and self.fund_trades_combo.currentText() != "" \
+ and self.value_input.value() != 0:
self.calc_but.setEnabled(True)
else:
self.calc_but.setEnabled(False)
# 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()
- self.fund_deal_fee = self.platform_win.get_fund_deal_fee()
- self.share_plat_fee = self.platform_win.get_share_plat_fee()
- self.share_plat_max_fee = self.platform_win.get_share_plat_max_fee()
- self.share_deal_fee = self.platform_win.get_share_deal_fee()
- self.share_deal_reduce_trades = self.platform_win.get_share_deal_reduce_trades()
- self.share_deal_reduce_amount = self.platform_win.get_share_deal_reduce_amount()
+ self.optional_boxes = self.platform_win.get_optional_boxes()
+ self.fund_plat_fee = self.platform_win.get_fund_plat_fee()
+ self.fund_deal_fee = self.platform_win.get_fund_deal_fee()
+ self.share_plat_fee = self.platform_win.get_share_plat_fee()
+ self.share_deal_fee = self.platform_win.get_share_deal_fee()
+
+ # TODO: This is HORRIBLE - find better way of doing it! (maybe enums?)
+ if self.optional_boxes[0]:
+ self.plat_name = self.platform_win.get_plat_name()
+ else:
+ self.plat_name = None
+
+ if self.optional_boxes[1]:
+ self.share_plat_max_fee = self.platform_win.get_share_plat_max_fee()
+ else:
+ self.share_plat_max_fee = None
+
+ if self.optional_boxes[2]:
+ self.share_deal_reduce_trades = self.platform_win.get_share_deal_reduce_trades()
+ else:
+ self.share_deal_reduce_trades = None
+
+ if self.optional_boxes[3]:
+ self.share_deal_reduce_amount = self.platform_win.get_share_deal_reduce_amount()
+ else:
+ self.share_deal_reduce_amount = None
# Calculate fees
- # TODO: Error checking on combo boxes
def calculate_fees(self):
self.init_variables()
# Set to zero each time to avoid persistence
@@ -98,13 +117,15 @@ class SIPPCompare(QMainWindow):
break
shares_value = (1 - (slider_val / 100)) * value_num
- if (self.share_plat_fee * shares_value / 12) > self.share_plat_max_fee:
+ if self.share_plat_max_fee is not None and \
+ (self.share_plat_fee * shares_value / 12) > self.share_plat_max_fee:
self.share_plat_fees = self.share_plat_max_fee * 12
else:
self.share_plat_fees = self.share_plat_fee * shares_value
share_trades_num = int(self.share_trades_combo.currentText())
- if (share_trades_num / 12) >= self.share_deal_reduce_trades:
+ if self.share_deal_reduce_trades is not None and \
+ (share_trades_num / 12) >= self.share_deal_reduce_trades:
self.share_deal_fees = self.share_deal_reduce_amount * share_trades_num
else:
self.share_deal_fees = self.share_deal_fee * share_trades_num
diff --git a/src/output_window.py b/src/output_window.py
index e9fcfb7..1765a51 100644
--- a/src/output_window.py
+++ b/src/output_window.py
@@ -23,7 +23,12 @@ class OutputWindow(QWidget):
cur_time = datetime.datetime.now()
if not os.path.exists("output"):
os.makedirs("output")
- filename_str = f"output/{self.platform_name}-{cur_time.year}.{cur_time.month}.{cur_time.day}.txt"
+ filename_str = f"output/"
+ if self.platform_name is not None:
+ filename_str += f"{self.platform_name}"
+ else:
+ filename_str += "Unnamed"
+ filename_str += f"-{cur_time.year}.{cur_time.month}.{cur_time.day}.txt"
output_file = open(filename_str, "wt", encoding = "utf-8")
output_file.write(self.results_str)
@@ -31,7 +36,10 @@ class OutputWindow(QWidget):
def display_output(self, fund_plat_fees: float, fund_deal_fees: float,
share_plat_fees: float, share_deal_fees: float, plat_name: str):
self.platform_name = plat_name
- self.results_str = f"Fees breakdown (Platform \"{self.platform_name}\"):"
+ if self.platform_name is not None:
+ self.results_str = f"Fees breakdown (Platform \"{self.platform_name}\"):"
+ else:
+ self.results_str = f"Fees breakdown:"
self.results_str += "\n\nPlatform fees:"
# :.2f is used in order to display 2 decimal places (currency form)
diff --git a/src/platform_edit.py b/src/platform_edit.py
index fda14dd..55f4e4a 100644
--- a/src/platform_edit.py
+++ b/src/platform_edit.py
@@ -1,4 +1,4 @@
-from PyQt6.QtCore import QRegularExpression, QEvent, QObject, QTimer
+from PyQt6.QtCore import QRegularExpression
from PyQt6.QtGui import QRegularExpressionValidator
from PyQt6.QtWidgets import QWidget
from PyQt6 import uic
@@ -13,6 +13,9 @@ class PlatformEdit(QWidget):
uic.loadUi("gui/platform_edit.ui", self)
# Initialise class variables
+ # Create main window object, passing this instance as param
+ self.main_win = main_window.SIPPCompare(self)
+
# TODO: Make fund_plat_fee user-defined
self.fund_plat_fee = [
[0, 250000, 1000000, 2000000],
@@ -30,23 +33,49 @@ class PlatformEdit(QWidget):
if autofill:
self.save_but.setEnabled(True)
- # Create main window object, passing this instance as param
- self.main_win = main_window.SIPPCompare(self)
+ self.required_fields = [
+ self.fund_deal_fee_box,
+ self.share_plat_fee_box,
+ self.share_deal_fee_box
+ ]
+
+ self.optional_fields = [
+ self.plat_name_box,
+ self.share_plat_max_fee_box,
+ self.share_deal_reduce_trades_box,
+ self.share_deal_reduce_amount_box
+ ]
+
+ self.optional_check_boxes = [
+ self.plat_name_check,
+ self.share_plat_max_fee_check,
+ self.share_deal_reduce_trades_check,
+ self.share_deal_reduce_amount_check
+ ]
+
+ self.check_boxes_ticked = [
+ True,
+ False,
+ False,
+ False
+ ]
# Handle events
+ for field in self.required_fields:
+ field.valueChanged.connect(self.check_valid)
+
+ for field in self.optional_fields:
+ field_type = field.staticMetaObject.className()
+ if field_type == "QLineEdit":
+ field.textChanged.connect(self.check_valid)
+ elif field_type == "FastEditQDoubleSpinBox" or field_type == "FastEditQSpinBox":
+ field.valueChanged.connect(self.check_valid)
+
+ for check_box in self.optional_check_boxes:
+ check_box.checkStateChanged.connect(self.check_valid)
+
# 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)
-
- # 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 _
@@ -77,33 +106,54 @@ class PlatformEdit(QWidget):
# Once user input is received show main window
self.main_win.show()
- # 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
+ # This method does multiple things in order to validate the user's inputs:
+ # 1) Check all required fields have a non-zero value
+ # 2) If an optional checkbox is toggled: toggle editing of the corresponding field
+ # 3) Check all optional fields the user has picked have a non-zero value
+ # 4) If the above two conditions are met (1 & 3), make the 'Save' button clickable
+ # 5) Keep a record of which optional fields the user has chosen to fill in
+ # It's called when an optional check box emits a checkStateChanged() signal
+ # It's also called when any field emits a textChanged() or valueChanged() signal
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:
+ # Check all required fields have a non-zero value
+ for field in self.required_fields:
+ if field.value() == 0:
valid = False
+ for i in range(len(self.optional_check_boxes)):
+ # Find the coordinates of the input box corresponding to the checkbox
+ # It will be on the same row, in the column to the left (-1)
+ check_box_idx = self.gridLayout.indexOf(self.optional_check_boxes[i])
+ check_box_pos = self.gridLayout.getItemPosition(check_box_idx)
+ input_box_pos = list(check_box_pos)[:2]
+ input_box_pos[1] -= 1
+ # Return copy of input field widget from its coordinates
+ input_box_item = self.gridLayout.itemAtPosition(input_box_pos[0], input_box_pos[1]).widget()
+ if self.optional_check_boxes[i].isChecked():
+ input_box_item.setEnabled(True)
+ self.check_boxes_ticked[i] = True
+ input_box_type = input_box_item.staticMetaObject.className()
+ if input_box_type == "QLineEdit":
+ if input_box_item.text() == "":
+ valid = False
+ elif input_box_type == "FastEditQDoubleSpinBox" or input_box_type == "FastEditQSpinBox":
+ if input_box_item.value() == 0:
+ valid = False
+ else:
+ input_box_item.setEnabled(False)
+ self.check_boxes_ticked[i] = 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_optional_boxes(self):
+ return self.check_boxes_ticked
+
def get_plat_name(self):
return self.plat_name
diff --git a/src/widgets/fastedit_spinbox.py b/src/widgets/fastedit_spinbox.py
new file mode 100644
index 0000000..dcdc692
--- /dev/null
+++ b/src/widgets/fastedit_spinbox.py
@@ -0,0 +1,13 @@
+from PyQt6.QtCore import QTimer
+from PyQt6.QtWidgets import QSpinBox, QDoubleSpinBox
+
+
+class FastEditQDoubleSpinBox(QDoubleSpinBox):
+ def focusInEvent(self, e):
+ QTimer.singleShot(0, self.selectAll)
+ super(FastEditQDoubleSpinBox, self).focusInEvent(e)
+
+class FastEditQSpinBox(QSpinBox):
+ def focusInEvent(self, e):
+ QTimer.singleShot(0, self.selectAll)
+ super(FastEditQSpinBox, self).focusInEvent(e)