From 9143afc1762f02918dc24380839aaf82cdf2da38 Mon Sep 17 00:00:00 2001 From: Roland W-H <r.weirhowell@gmail.com> Date: Tue, 11 Feb 2025 10:57:19 +0000 Subject: [PATCH 1/8] begin adding optional fields --- gui/platform_edit.ui | 201 ++++++++++++++++++++++++++++++------------- src/platform_edit.py | 18 +++- 2 files changed, 156 insertions(+), 63 deletions(-) diff --git a/gui/platform_edit.ui b/gui/platform_edit.ui index 0cc4a1f..6233405 100644 --- a/gui/platform_edit.ui +++ b/gui/platform_edit.ui @@ -53,13 +53,6 @@ <property name="verticalSpacing"> <number>5</number> </property> - <item row="0" column="0" colspan="2"> - <widget class="QLabel" name="plat_name_lab"> - <property name="text"> - <string>Platform name</string> - </property> - </widget> - </item> <item row="8" column="0" colspan="2"> <widget class="QLabel" name="share_deal_reduce_amount_lab"> <property name="text"> @@ -67,44 +60,6 @@ </property> </widget> </item> - <item row="0" column="2"> - <widget class="QLineEdit" name="plat_name_box"/> - </item> - <item row="3" column="0" colspan="2"> - <widget class="QLabel" name="share_plat_fee_lab"> - <property name="text"> - <string>Share platform fee*</string> - </property> - </widget> - </item> - <item row="7" column="0" colspan="2"> - <widget class="QLabel" name="share_deal_reduce_trades_lab"> - <property name="text"> - <string>Share dealing discount # of trades</string> - </property> - </widget> - </item> - <item row="5" column="0" colspan="2"> - <widget class="QLabel" name="share_deal_fee_lab"> - <property name="text"> - <string>Share dealing fee*</string> - </property> - </widget> - </item> - <item row="1" column="0" colspan="2"> - <widget class="QLabel" name="fund_deal_fee_lab"> - <property name="text"> - <string>Fund dealing fee*</string> - </property> - </widget> - </item> - <item row="4" column="0" colspan="2"> - <widget class="QLabel" name="share_plat_max_fee_lab"> - <property name="text"> - <string>Share platform fee cap/mth</string> - </property> - </widget> - </item> <item row="8" column="2"> <widget class="QDoubleSpinBox" name="share_deal_reduce_amount_box"> <property name="buttonSymbols"> @@ -118,19 +73,6 @@ </property> </widget> </item> - <item row="5" column="2"> - <widget class="QDoubleSpinBox" name="share_deal_fee_box"> - <property name="buttonSymbols"> - <enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum> - </property> - <property name="correctionMode"> - <enum>QAbstractSpinBox::CorrectionMode::CorrectToNearestValue</enum> - </property> - <property name="prefix"> - <string>£</string> - </property> - </widget> - </item> <item row="4" column="2"> <widget class="QDoubleSpinBox" name="share_plat_max_fee_box"> <property name="buttonSymbols"> @@ -144,16 +86,49 @@ </property> </widget> </item> - <item row="3" column="2"> - <widget class="QDoubleSpinBox" name="share_plat_fee_box"> + <item row="0" column="3"> + <widget class="QCheckBox" name="plat_name_check"> + <property name="text"> + <string/> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="0" colspan="2"> + <widget class="QLabel" name="plat_name_lab"> + <property name="text"> + <string>Platform name</string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLineEdit" name="plat_name_box"/> + </item> + <item row="3" column="3"> + <widget class="QCheckBox" name="share_plat_fee_check"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string/> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="5" column="2"> + <widget class="QDoubleSpinBox" name="share_deal_fee_box"> <property name="buttonSymbols"> <enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum> </property> <property name="correctionMode"> <enum>QAbstractSpinBox::CorrectionMode::CorrectToNearestValue</enum> </property> - <property name="suffix"> - <string>%</string> + <property name="prefix"> + <string>£</string> </property> </widget> </item> @@ -170,6 +145,47 @@ </property> </widget> </item> + <item row="1" column="0" colspan="2"> + <widget class="QLabel" name="fund_deal_fee_lab"> + <property name="text"> + <string>Fund dealing fee*</string> + </property> + </widget> + </item> + <item row="4" column="0" colspan="2"> + <widget class="QLabel" name="share_plat_max_fee_lab"> + <property name="text"> + <string>Share platform fee cap/mth</string> + </property> + </widget> + </item> + <item row="3" column="2"> + <widget class="QDoubleSpinBox" name="share_plat_fee_box"> + <property name="buttonSymbols"> + <enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum> + </property> + <property name="correctionMode"> + <enum>QAbstractSpinBox::CorrectionMode::CorrectToNearestValue</enum> + </property> + <property name="suffix"> + <string>%</string> + </property> + </widget> + </item> + <item row="7" column="3"> + <widget class="QCheckBox" name="share_deal_reduce_trades_check"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="5" column="0" colspan="2"> + <widget class="QLabel" name="share_deal_fee_lab"> + <property name="text"> + <string>Share dealing fee*</string> + </property> + </widget> + </item> <item row="7" column="2"> <widget class="QSpinBox" name="share_deal_reduce_trades_box"> <property name="buttonSymbols"> @@ -180,6 +196,60 @@ </property> </widget> </item> + <item row="7" column="0" colspan="2"> + <widget class="QLabel" name="share_deal_reduce_trades_lab"> + <property name="text"> + <string>Share dealing discount # of trades</string> + </property> + </widget> + </item> + <item row="8" column="3"> + <widget class="QCheckBox" name="share_deal_reduce_amount_check"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="5" column="3"> + <widget class="QCheckBox" name="share_deal_fee_check"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string/> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="3" column="0" colspan="2"> + <widget class="QLabel" name="share_plat_fee_lab"> + <property name="text"> + <string>Share platform fee*</string> + </property> + </widget> + </item> + <item row="1" column="3"> + <widget class="QCheckBox" name="fund_deal_fee_check"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string/> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="4" column="3"> + <widget class="QCheckBox" name="share_plat_max_fee_check"> + <property name="text"> + <string/> + </property> + </widget> + </item> </layout> </widget> <widget class="QPushButton" name="save_but"> @@ -221,6 +291,13 @@ <tabstop>share_deal_reduce_trades_box</tabstop> <tabstop>share_deal_reduce_amount_box</tabstop> <tabstop>save_but</tabstop> + <tabstop>plat_name_check</tabstop> + <tabstop>fund_deal_fee_check</tabstop> + <tabstop>share_plat_fee_check</tabstop> + <tabstop>share_plat_max_fee_check</tabstop> + <tabstop>share_deal_fee_check</tabstop> + <tabstop>share_deal_reduce_trades_check</tabstop> + <tabstop>share_deal_reduce_amount_check</tabstop> </tabstops> <resources/> <connections> diff --git a/src/platform_edit.py b/src/platform_edit.py index fda14dd..7989e83 100644 --- a/src/platform_edit.py +++ b/src/platform_edit.py @@ -30,6 +30,13 @@ class PlatformEdit(QWidget): if autofill: self.save_but.setEnabled(True) + 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 + ] + # Create main window object, passing this instance as param self.main_win = main_window.SIPPCompare(self) @@ -40,6 +47,9 @@ class PlatformEdit(QWidget): self.share_plat_fee_box.valueChanged.connect(self.check_valid) self.share_deal_fee_box.valueChanged.connect(self.check_valid) + for check_box in self.optional_check_boxes: + check_box.checkStateChanged.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) @@ -48,6 +58,9 @@ class PlatformEdit(QWidget): self.share_deal_reduce_trades_box.installEventFilter(self) self.share_deal_reduce_amount_box.installEventFilter(self) + #for check_box in self.optional_check_boxes: + # check_box.installEventFilter(self) + # Set validators # Regex accepts any characters that match [a-Z], [0-9] or _ self.plat_name_box.setValidator( @@ -79,10 +92,13 @@ class PlatformEdit(QWidget): # 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: + if obj.staticMetaObject.className() == "QDoubleSpinBox" and 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) + #if obj in self.optional_check_boxes and \ + #event.type() == QEvent.Type.FocusIn or event.type() == QEvent.Type.FocusOut: + # print("Working") return False # Check if all required fields have valid (non-zero) input From ecc2ce6d566e9ec6cb15df35d5147e6e088d4d34 Mon Sep 17 00:00:00 2001 From: Roland W-H <r.weirhowell@gmail.com> Date: Tue, 11 Feb 2025 13:42:35 +0000 Subject: [PATCH 2/8] continue implementing optional fields - still broken --- src/platform_edit.py | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/src/platform_edit.py b/src/platform_edit.py index 7989e83..e5dd0f9 100644 --- a/src/platform_edit.py +++ b/src/platform_edit.py @@ -1,6 +1,6 @@ from PyQt6.QtCore import QRegularExpression, QEvent, QObject, QTimer from PyQt6.QtGui import QRegularExpressionValidator -from PyQt6.QtWidgets import QWidget +from PyQt6.QtWidgets import QWidget, QLayout from PyQt6 import uic import main_window @@ -37,6 +37,12 @@ class PlatformEdit(QWidget): self.share_deal_reduce_amount_check ] + self.required_fields = [ + self.fund_deal_fee_box, + self.share_plat_fee_box, + self.share_deal_fee_box + ] + # Create main window object, passing this instance as param self.main_win = main_window.SIPPCompare(self) @@ -104,16 +110,27 @@ class PlatformEdit(QWidget): # 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: + for field in self.required_fields: + if field.value() == 0: valid = False + for check_box in self.optional_check_boxes: + if check_box.isChecked(): + check_box_pos = self.gridLayout.getItemPosition( + self.gridLayout.indexOf(check_box) + ) + input_box_pos = list(check_box_pos)[:2] + input_box_pos[1] -= 1 + input_box_item = self.gridLayout.itemAtPosition(input_box_pos[0], input_box_pos[1]).widget() + if input_box_item.staticMetaObject.className() == "QLineEdit": + if input_box_item.text() == "": + valid = False + elif input_box_item.staticMetaObject.className() == "QDoubleSpinBox": + if input_box_item.value() == 0: + valid = False + if valid: self.save_but.setEnabled(True) else: From fcbc78b05b2448c6d97555ff77801566f8c45088 Mon Sep 17 00:00:00 2001 From: Roland W-H <r.weirhowell@gmail.com> Date: Tue, 11 Feb 2025 15:52:36 +0000 Subject: [PATCH 3/8] opt field input validation complete - functionality still TODO --- gui/platform_edit.ui | 9 ++++++++ src/platform_edit.py | 55 ++++++++++++++++++++++++++------------------ 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/gui/platform_edit.ui b/gui/platform_edit.ui index 6233405..abd0088 100644 --- a/gui/platform_edit.ui +++ b/gui/platform_edit.ui @@ -62,6 +62,9 @@ </item> <item row="8" column="2"> <widget class="QDoubleSpinBox" name="share_deal_reduce_amount_box"> + <property name="enabled"> + <bool>false</bool> + </property> <property name="buttonSymbols"> <enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum> </property> @@ -75,6 +78,9 @@ </item> <item row="4" column="2"> <widget class="QDoubleSpinBox" name="share_plat_max_fee_box"> + <property name="enabled"> + <bool>false</bool> + </property> <property name="buttonSymbols"> <enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum> </property> @@ -188,6 +194,9 @@ </item> <item row="7" column="2"> <widget class="QSpinBox" name="share_deal_reduce_trades_box"> + <property name="enabled"> + <bool>false</bool> + </property> <property name="buttonSymbols"> <enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum> </property> diff --git a/src/platform_edit.py b/src/platform_edit.py index e5dd0f9..846551d 100644 --- a/src/platform_edit.py +++ b/src/platform_edit.py @@ -30,6 +30,19 @@ class PlatformEdit(QWidget): if autofill: self.save_but.setEnabled(True) + 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, @@ -37,21 +50,21 @@ class PlatformEdit(QWidget): self.share_deal_reduce_amount_check ] - self.required_fields = [ - self.fund_deal_fee_box, - self.share_plat_fee_box, - self.share_deal_fee_box - ] - # Create main window object, passing this instance as param self.main_win = main_window.SIPPCompare(self) # Handle events # 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) + + for field in self.required_fields: + field.valueChanged.connect(self.check_valid) + + for field in self.optional_fields: + if field.staticMetaObject.className() == "QLineEdit": + field.textChanged.connect(self.check_valid) + elif field.staticMetaObject.className() == "QDoubleSpinBox": + field.valueChanged.connect(self.check_valid) for check_box in self.optional_check_boxes: check_box.checkStateChanged.connect(self.check_valid) @@ -98,17 +111,13 @@ class PlatformEdit(QWidget): # When focus is given to an input box, select all text in it (easier to edit) def eventFilter(self, obj: QObject, event: QEvent): - if obj.staticMetaObject.className() == "QDoubleSpinBox" and event.type() == QEvent.Type.FocusIn: + 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) - #if obj in self.optional_check_boxes and \ - #event.type() == QEvent.Type.FocusIn or event.type() == QEvent.Type.FocusOut: - # print("Working") 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): valid = True @@ -117,19 +126,21 @@ class PlatformEdit(QWidget): valid = False for check_box in self.optional_check_boxes: + check_box_pos = self.gridLayout.getItemPosition(self.gridLayout.indexOf(check_box)) + input_box_pos = list(check_box_pos)[:2] + input_box_pos[1] -= 1 + input_box_item = self.gridLayout.itemAtPosition(input_box_pos[0], input_box_pos[1]).widget() if check_box.isChecked(): - check_box_pos = self.gridLayout.getItemPosition( - self.gridLayout.indexOf(check_box) - ) - input_box_pos = list(check_box_pos)[:2] - input_box_pos[1] -= 1 - input_box_item = self.gridLayout.itemAtPosition(input_box_pos[0], input_box_pos[1]).widget() - if input_box_item.staticMetaObject.className() == "QLineEdit": + input_box_item.setEnabled(True) + input_box_type = input_box_item.staticMetaObject.className() + if input_box_type == "QLineEdit": if input_box_item.text() == "": valid = False - elif input_box_item.staticMetaObject.className() == "QDoubleSpinBox": + elif input_box_type == "QDoubleSpinBox" or input_box_type == "QSpinBox": if input_box_item.value() == 0: valid = False + else: + input_box_item.setEnabled(False) if valid: self.save_but.setEnabled(True) From 36a36f5437441b3416cec10c84a8181b89716ac0 Mon Sep 17 00:00:00 2001 From: Roland W-H <r.weirhowell@gmail.com> Date: Tue, 11 Feb 2025 18:11:54 +0000 Subject: [PATCH 4/8] implemented optional fields - needs further work --- src/main_window.py | 42 +++++++++++++++++++++++--------- src/output_window.py | 12 ++++++++-- src/platform_edit.py | 57 ++++++++++++++++++++++++++++++++------------ 3 files changed, 83 insertions(+), 28 deletions(-) diff --git a/src/main_window.py b/src/main_window.py index 14060e3..a012427 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 @@ -62,17 +63,34 @@ class SIPPCompare(QMainWindow): # 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 +116,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 846551d..2c076ef 100644 --- a/src/platform_edit.py +++ b/src/platform_edit.py @@ -1,6 +1,6 @@ from PyQt6.QtCore import QRegularExpression, QEvent, QObject, QTimer from PyQt6.QtGui import QRegularExpressionValidator -from PyQt6.QtWidgets import QWidget, QLayout +from PyQt6.QtWidgets import QWidget from PyQt6 import uic import main_window @@ -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], @@ -50,35 +53,42 @@ class PlatformEdit(QWidget): self.share_deal_reduce_amount_check ] - # Create main window object, passing this instance as param - self.main_win = main_window.SIPPCompare(self) + self.check_boxes_ticked = [ + True, + False, + False, + False + ] # Handle events - # NOTE: Signal defined in UI file to close window when save button clicked - self.save_but.clicked.connect(self.init_variables) - for field in self.required_fields: field.valueChanged.connect(self.check_valid) + field.installEventFilter(self) for field in self.optional_fields: - if field.staticMetaObject.className() == "QLineEdit": + field_type = field.staticMetaObject.className() + if field_type == "QLineEdit": field.textChanged.connect(self.check_valid) - elif field.staticMetaObject.className() == "QDoubleSpinBox": + elif field_type == "QDoubleSpinBox" or field_type == "QSpinBox": field.valueChanged.connect(self.check_valid) + field.installEventFilter(self) 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) + # Install event filter on input boxes in order to select all text on focus + # TODO: Seems like my eventFilter() override is capturing all events - need to stop this + """ 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) - - #for check_box in self.optional_check_boxes: - # check_box.installEventFilter(self) + """ # Set validators # Regex accepts any characters that match [a-Z], [0-9] or _ @@ -117,21 +127,34 @@ class PlatformEdit(QWidget): QTimer.singleShot(0, obj.selectAll) return False - # Check if all required fields have valid (non-zero) input + # 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): valid = True + # Check all required fields have a non-zero value for field in self.required_fields: if field.value() == 0: valid = False - for check_box in self.optional_check_boxes: - check_box_pos = self.gridLayout.getItemPosition(self.gridLayout.indexOf(check_box)) + 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 check_box.isChecked(): + 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() == "": @@ -141,6 +164,7 @@ class PlatformEdit(QWidget): valid = False else: input_box_item.setEnabled(False) + self.check_boxes_ticked[i] = False if valid: self.save_but.setEnabled(True) @@ -148,6 +172,9 @@ class PlatformEdit(QWidget): 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 From 107c388afba04721b7856a01548e4a6235458dc6 Mon Sep 17 00:00:00 2001 From: Roland W-H <r.weirhowell@gmail.com> Date: Tue, 11 Feb 2025 19:04:21 +0000 Subject: [PATCH 5/8] add label hint for checkboxes, fix tab order --- gui/platform_edit.ui | 232 +++++++++++++++++++++---------------------- 1 file changed, 112 insertions(+), 120 deletions(-) diff --git a/gui/platform_edit.ui b/gui/platform_edit.ui index abd0088..446c309 100644 --- a/gui/platform_edit.ui +++ b/gui/platform_edit.ui @@ -6,20 +6,20 @@ <rect> <x>0</x> <y>0</y> - <width>480</width> - <height>305</height> + <width>498</width> + <height>303</height> </rect> </property> <property name="minimumSize"> <size> - <width>480</width> - <height>305</height> + <width>498</width> + <height>303</height> </size> </property> <property name="maximumSize"> <size> - <width>480</width> - <height>305</height> + <width>498</width> + <height>303</height> </size> </property> <property name="windowTitle"> @@ -28,10 +28,10 @@ <widget class="QWidget" name="gridLayoutWidget"> <property name="geometry"> <rect> - <x>10</x> - <y>10</y> + <x>20</x> + <y>20</y> <width>461</width> - <height>251</height> + <height>243</height> </rect> </property> <layout class="QGridLayout" name="gridLayout"> @@ -53,30 +53,24 @@ <property name="verticalSpacing"> <number>5</number> </property> - <item row="8" column="0" colspan="2"> - <widget class="QLabel" name="share_deal_reduce_amount_lab"> - <property name="text"> - <string>Share dealing discount amount</string> + <item row="0" column="3"> + <widget class="QCheckBox" name="plat_name_check"> + <property name="checked"> + <bool>true</bool> </property> </widget> </item> - <item row="8" column="2"> - <widget class="QDoubleSpinBox" name="share_deal_reduce_amount_box"> + <item row="6" column="3"> + <widget class="QCheckBox" name="share_deal_fee_check"> <property name="enabled"> <bool>false</bool> </property> - <property name="buttonSymbols"> - <enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum> - </property> - <property name="correctionMode"> - <enum>QAbstractSpinBox::CorrectionMode::CorrectToNearestValue</enum> - </property> - <property name="prefix"> - <string>£</string> + <property name="checked"> + <bool>true</bool> </property> </widget> </item> - <item row="4" column="2"> + <item row="5" column="2"> <widget class="QDoubleSpinBox" name="share_plat_max_fee_box"> <property name="enabled"> <bool>false</bool> @@ -92,40 +86,17 @@ </property> </widget> </item> - <item row="0" column="3"> - <widget class="QCheckBox" name="plat_name_check"> - <property name="text"> - <string/> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="0" column="0" colspan="2"> - <widget class="QLabel" name="plat_name_lab"> - <property name="text"> - <string>Platform name</string> - </property> - </widget> - </item> - <item row="0" column="2"> - <widget class="QLineEdit" name="plat_name_box"/> - </item> - <item row="3" column="3"> - <widget class="QCheckBox" name="share_plat_fee_check"> + <item row="2" column="3"> + <widget class="QCheckBox" name="fund_deal_fee_check"> <property name="enabled"> <bool>false</bool> </property> - <property name="text"> - <string/> - </property> <property name="checked"> <bool>true</bool> </property> </widget> </item> - <item row="5" column="2"> + <item row="6" column="2"> <widget class="QDoubleSpinBox" name="share_deal_fee_box"> <property name="buttonSymbols"> <enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum> @@ -138,7 +109,24 @@ </property> </widget> </item> - <item row="1" column="2"> + <item row="8" column="0" colspan="2"> + <widget class="QLabel" name="share_deal_reduce_trades_lab"> + <property name="text"> + <string>Share dealing discount # of trades</string> + </property> + </widget> + </item> + <item row="9" column="3"> + <widget class="QCheckBox" name="share_deal_reduce_amount_check"/> + </item> + <item row="5" column="0" colspan="2"> + <widget class="QLabel" name="share_plat_max_fee_lab"> + <property name="text"> + <string>Share platform fee cap/mth</string> + </property> + </widget> + </item> + <item row="2" column="2"> <widget class="QDoubleSpinBox" name="fund_deal_fee_box"> <property name="buttonSymbols"> <enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum> @@ -151,48 +139,14 @@ </property> </widget> </item> - <item row="1" column="0" colspan="2"> - <widget class="QLabel" name="fund_deal_fee_lab"> - <property name="text"> - <string>Fund dealing fee*</string> - </property> - </widget> - </item> - <item row="4" column="0" colspan="2"> - <widget class="QLabel" name="share_plat_max_fee_lab"> - <property name="text"> - <string>Share platform fee cap/mth</string> - </property> - </widget> - </item> - <item row="3" column="2"> - <widget class="QDoubleSpinBox" name="share_plat_fee_box"> - <property name="buttonSymbols"> - <enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum> - </property> - <property name="correctionMode"> - <enum>QAbstractSpinBox::CorrectionMode::CorrectToNearestValue</enum> - </property> - <property name="suffix"> - <string>%</string> - </property> - </widget> - </item> - <item row="7" column="3"> - <widget class="QCheckBox" name="share_deal_reduce_trades_check"> - <property name="text"> - <string/> - </property> - </widget> - </item> - <item row="5" column="0" colspan="2"> + <item row="6" column="0" colspan="2"> <widget class="QLabel" name="share_deal_fee_lab"> <property name="text"> <string>Share dealing fee*</string> </property> </widget> </item> - <item row="7" column="2"> + <item row="8" column="2"> <widget class="QSpinBox" name="share_deal_reduce_trades_box"> <property name="enabled"> <bool>false</bool> @@ -205,57 +159,79 @@ </property> </widget> </item> - <item row="7" column="0" colspan="2"> - <widget class="QLabel" name="share_deal_reduce_trades_lab"> - <property name="text"> - <string>Share dealing discount # of trades</string> - </property> - </widget> - </item> <item row="8" column="3"> - <widget class="QCheckBox" name="share_deal_reduce_amount_check"> - <property name="text"> - <string/> - </property> - </widget> + <widget class="QCheckBox" name="share_deal_reduce_trades_check"/> </item> - <item row="5" column="3"> - <widget class="QCheckBox" name="share_deal_fee_check"> + <item row="4" column="3"> + <widget class="QCheckBox" name="share_plat_fee_check"> <property name="enabled"> <bool>false</bool> </property> - <property name="text"> - <string/> - </property> <property name="checked"> <bool>true</bool> </property> </widget> </item> - <item row="3" column="0" colspan="2"> + <item row="2" column="0" colspan="2"> + <widget class="QLabel" name="fund_deal_fee_lab"> + <property name="text"> + <string>Fund dealing fee*</string> + </property> + </widget> + </item> + <item row="5" column="3"> + <widget class="QCheckBox" name="share_plat_max_fee_check"/> + </item> + <item row="0" column="2"> + <widget class="QLineEdit" name="plat_name_box"/> + </item> + <item row="9" column="0" colspan="2"> + <widget class="QLabel" name="share_deal_reduce_amount_lab"> + <property name="text"> + <string>Share dealing discount amount</string> + </property> + </widget> + </item> + <item row="4" column="0" colspan="2"> <widget class="QLabel" name="share_plat_fee_lab"> <property name="text"> <string>Share platform fee*</string> </property> </widget> </item> - <item row="1" column="3"> - <widget class="QCheckBox" name="fund_deal_fee_check"> - <property name="enabled"> - <bool>false</bool> + <item row="4" column="2"> + <widget class="QDoubleSpinBox" name="share_plat_fee_box"> + <property name="buttonSymbols"> + <enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum> </property> - <property name="text"> - <string/> + <property name="correctionMode"> + <enum>QAbstractSpinBox::CorrectionMode::CorrectToNearestValue</enum> </property> - <property name="checked"> - <bool>true</bool> + <property name="suffix"> + <string>%</string> </property> </widget> </item> - <item row="4" column="3"> - <widget class="QCheckBox" name="share_plat_max_fee_check"> + <item row="0" column="0" colspan="2"> + <widget class="QLabel" name="plat_name_lab"> <property name="text"> - <string/> + <string>Platform name</string> + </property> + </widget> + </item> + <item row="9" column="2"> + <widget class="QDoubleSpinBox" name="share_deal_reduce_amount_box"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="buttonSymbols"> + <enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum> + </property> + <property name="correctionMode"> + <enum>QAbstractSpinBox::CorrectionMode::CorrectToNearestValue</enum> + </property> + <property name="prefix"> + <string>£</string> </property> </widget> </item> @@ -267,7 +243,7 @@ </property> <property name="geometry"> <rect> - <x>330</x> + <x>350</x> <y>270</y> <width>141</width> <height>24</height> @@ -277,11 +253,11 @@ <string>Save</string> </property> </widget> - <widget class="QLabel" name="label"> + <widget class="QLabel" name="req_field_lab"> <property name="geometry"> <rect> - <x>20</x> - <y>270</y> + <x>10</x> + <y>280</y> <width>191</width> <height>21</height> </rect> @@ -290,6 +266,22 @@ <string>* Indicates required field</string> </property> </widget> + <widget class="QLabel" name="active_lab"> + <property name="geometry"> + <rect> + <x>437</x> + <y>10</y> + <width>61</width> + <height>16</height> + </rect> + </property> + <property name="text"> + <string>Active?</string> + </property> + <property name="alignment"> + <set>Qt::AlignmentFlag::AlignCenter</set> + </property> + </widget> </widget> <tabstops> <tabstop>plat_name_box</tabstop> From 9ef60486019ff4d568111b83cdc692ec90633d5f Mon Sep 17 00:00:00 2001 From: Roland W-H <r.weirhowell@gmail.com> Date: Tue, 11 Feb 2025 19:31:13 +0000 Subject: [PATCH 6/8] do input validation on pension value field --- src/main_window.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main_window.py b/src/main_window.py index a012427..093e511 100644 --- a/src/main_window.py +++ b/src/main_window.py @@ -40,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) @@ -56,7 +56,8 @@ 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) From 68ac9924836c5cad29ee74ab13a423b40ae63cb0 Mon Sep 17 00:00:00 2001 From: Roland W-H <r.weirhowell@gmail.com> Date: Tue, 11 Feb 2025 21:19:05 +0000 Subject: [PATCH 7/8] implement custom Q(Double)SpinBox widget for onFocus selection --- gui/platform_edit.ui | 26 +++++++++++++++++++------- src/platform_edit.py | 20 +++++--------------- src/widgets/fastedit_spinbox.py | 13 +++++++++++++ 3 files changed, 37 insertions(+), 22 deletions(-) create mode 100644 src/widgets/fastedit_spinbox.py diff --git a/gui/platform_edit.ui b/gui/platform_edit.ui index 446c309..9302c89 100644 --- a/gui/platform_edit.ui +++ b/gui/platform_edit.ui @@ -71,7 +71,7 @@ </widget> </item> <item row="5" column="2"> - <widget class="QDoubleSpinBox" name="share_plat_max_fee_box"> + <widget class="FastEditQDoubleSpinBox" name="share_plat_max_fee_box"> <property name="enabled"> <bool>false</bool> </property> @@ -97,7 +97,7 @@ </widget> </item> <item row="6" column="2"> - <widget class="QDoubleSpinBox" name="share_deal_fee_box"> + <widget class="FastEditQDoubleSpinBox" name="share_deal_fee_box"> <property name="buttonSymbols"> <enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum> </property> @@ -122,12 +122,12 @@ <item row="5" column="0" colspan="2"> <widget class="QLabel" name="share_plat_max_fee_lab"> <property name="text"> - <string>Share platform fee cap/mth</string> + <string>Share platform monthly fee cap</string> </property> </widget> </item> <item row="2" column="2"> - <widget class="QDoubleSpinBox" name="fund_deal_fee_box"> + <widget class="FastEditQDoubleSpinBox" name="fund_deal_fee_box"> <property name="buttonSymbols"> <enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum> </property> @@ -147,7 +147,7 @@ </widget> </item> <item row="8" column="2"> - <widget class="QSpinBox" name="share_deal_reduce_trades_box"> + <widget class="FastEditQSpinBox" name="share_deal_reduce_trades_box"> <property name="enabled"> <bool>false</bool> </property> @@ -200,7 +200,7 @@ </widget> </item> <item row="4" column="2"> - <widget class="QDoubleSpinBox" name="share_plat_fee_box"> + <widget class="FastEditQDoubleSpinBox" name="share_plat_fee_box"> <property name="buttonSymbols"> <enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum> </property> @@ -220,7 +220,7 @@ </widget> </item> <item row="9" column="2"> - <widget class="QDoubleSpinBox" name="share_deal_reduce_amount_box"> + <widget class="FastEditQDoubleSpinBox" name="share_deal_reduce_amount_box"> <property name="enabled"> <bool>false</bool> </property> @@ -283,6 +283,18 @@ </property> </widget> </widget> + <customwidgets> + <customwidget> + <class>FastEditQDoubleSpinBox</class> + <extends>QDoubleSpinBox</extends> + <header>widgets/fastedit_spinbox</header> + </customwidget> + <customwidget> + <class>FastEditQSpinBox</class> + <extends>QSpinBox</extends> + <header>widgets/fastedit_spinbox</header> + </customwidget> + </customwidgets> <tabstops> <tabstop>plat_name_box</tabstop> <tabstop>fund_deal_fee_box</tabstop> diff --git a/src/platform_edit.py b/src/platform_edit.py index 2c076ef..c3b7298 100644 --- a/src/platform_edit.py +++ b/src/platform_edit.py @@ -1,6 +1,6 @@ from PyQt6.QtCore import QRegularExpression, QEvent, QObject, QTimer from PyQt6.QtGui import QRegularExpressionValidator -from PyQt6.QtWidgets import QWidget +from PyQt6.QtWidgets import QWidget, QDoubleSpinBox from PyQt6 import uic import main_window @@ -79,17 +79,6 @@ class PlatformEdit(QWidget): # NOTE: Signal defined in UI file to close window when save button clicked self.save_but.clicked.connect(self.init_variables) - # Install event filter on input boxes in order to select all text on focus - # TODO: Seems like my eventFilter() override is capturing all events - need to stop this - """ - 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( @@ -120,11 +109,12 @@ class PlatformEdit(QWidget): 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: + def focusInEvent(self, event): + print("yay") + if event.gotFocus(): # Alternative condition for % suffix - currently unused #if obj.value() == 0 or obj == self.share_plat_fee_box: - QTimer.singleShot(0, obj.selectAll) + QTimer.singleShot(0, sender.selectAll) return False # This method does multiple things in order to validate the user's inputs: 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) From 679a509101ea249e20601353b4804718f8de68af Mon Sep 17 00:00:00 2001 From: Roland W-H <r.weirhowell@gmail.com> Date: Mon, 17 Feb 2025 10:05:38 +0000 Subject: [PATCH 8/8] fix input validation to work with custom widgets --- gui/platform_edit.ui | 18 ++++++++++++++++++ src/platform_edit.py | 19 ++++--------------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/gui/platform_edit.ui b/gui/platform_edit.ui index 9302c89..ddf9a0e 100644 --- a/gui/platform_edit.ui +++ b/gui/platform_edit.ui @@ -84,6 +84,9 @@ <property name="prefix"> <string>£</string> </property> + <property name="maximum"> + <double>999.000000000000000</double> + </property> </widget> </item> <item row="2" column="3"> @@ -107,6 +110,9 @@ <property name="prefix"> <string>£</string> </property> + <property name="maximum"> + <double>999.000000000000000</double> + </property> </widget> </item> <item row="8" column="0" colspan="2"> @@ -137,6 +143,9 @@ <property name="prefix"> <string>£</string> </property> + <property name="maximum"> + <double>999.000000000000000</double> + </property> </widget> </item> <item row="6" column="0" colspan="2"> @@ -157,6 +166,9 @@ <property name="correctionMode"> <enum>QAbstractSpinBox::CorrectionMode::CorrectToNearestValue</enum> </property> + <property name="maximum"> + <number>999</number> + </property> </widget> </item> <item row="8" column="3"> @@ -210,6 +222,9 @@ <property name="suffix"> <string>%</string> </property> + <property name="maximum"> + <double>99.000000000000000</double> + </property> </widget> </item> <item row="0" column="0" colspan="2"> @@ -233,6 +248,9 @@ <property name="prefix"> <string>£</string> </property> + <property name="maximum"> + <double>999.000000000000000</double> + </property> </widget> </item> </layout> diff --git a/src/platform_edit.py b/src/platform_edit.py index c3b7298..55f4e4a 100644 --- a/src/platform_edit.py +++ b/src/platform_edit.py @@ -1,6 +1,6 @@ -from PyQt6.QtCore import QRegularExpression, QEvent, QObject, QTimer +from PyQt6.QtCore import QRegularExpression from PyQt6.QtGui import QRegularExpressionValidator -from PyQt6.QtWidgets import QWidget, QDoubleSpinBox +from PyQt6.QtWidgets import QWidget from PyQt6 import uic import main_window @@ -63,15 +63,13 @@ class PlatformEdit(QWidget): # Handle events for field in self.required_fields: field.valueChanged.connect(self.check_valid) - field.installEventFilter(self) for field in self.optional_fields: field_type = field.staticMetaObject.className() if field_type == "QLineEdit": field.textChanged.connect(self.check_valid) - elif field_type == "QDoubleSpinBox" or field_type == "QSpinBox": + elif field_type == "FastEditQDoubleSpinBox" or field_type == "FastEditQSpinBox": field.valueChanged.connect(self.check_valid) - field.installEventFilter(self) for check_box in self.optional_check_boxes: check_box.checkStateChanged.connect(self.check_valid) @@ -108,15 +106,6 @@ 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 focusInEvent(self, event): - print("yay") - if event.gotFocus(): - # Alternative condition for % suffix - currently unused - #if obj.value() == 0 or obj == self.share_plat_fee_box: - QTimer.singleShot(0, sender.selectAll) - return False - # 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 @@ -149,7 +138,7 @@ class PlatformEdit(QWidget): if input_box_type == "QLineEdit": if input_box_item.text() == "": valid = False - elif input_box_type == "QDoubleSpinBox" or input_box_type == "QSpinBox": + elif input_box_type == "FastEditQDoubleSpinBox" or input_box_type == "FastEditQSpinBox": if input_box_item.value() == 0: valid = False else: