From 929c3719c0c0942664970a486a63d22e649574e2 Mon Sep 17 00:00:00 2001 From: Roland W-H Date: Thu, 24 Apr 2025 22:55:26 +0100 Subject: [PATCH] complete db & graph implementation (except for delete) --- SIPPCompare.db | Bin 53248 -> 45056 bytes gui/dialogs/platform_rename.ui | 70 ++++++++++++++++------ gui/output_window.ui | 103 ++++++--------------------------- gui/platform_edit.ui | 3 + gui/platform_list.ui | 22 ++++++- src/db_handler.py | 93 +++++++++++++++++++++++++++-- src/main.py | 21 ++----- src/main_window.py | 99 ++++++++++++++++--------------- src/output_window.py | 31 ++++++++-- src/platform_edit.py | 103 ++++++++++++++++++--------------- src/platform_list.py | 81 +++++++++++++++++++------- src/widgets/mpl_widget.py | 14 +++++ 12 files changed, 396 insertions(+), 244 deletions(-) create mode 100644 src/widgets/mpl_widget.py diff --git a/SIPPCompare.db b/SIPPCompare.db index ef44f752cfd449b182e401ab5439891013c26e56..0e2d14c8a24c39ba03f31c72c2299c67a47d8281 100644 GIT binary patch literal 45056 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 PlatformRename - + 0 0 300 - 90 + 100 - Rename Platform + Name Platform - + - 221 - 62 - 75 - 24 + 7 + 41 + 287 + 22 - - OK + + + 11 + - 60 - 10 - 191 + 35 + 9 + 241 20 + + + 11 + + Enter a new name for the platform - + - 8 - 34 - 287 - 22 + 220 + 70 + 75 + 24 + + + 10 + + + + OK + - + + + rename_plat_ok_but + clicked() + PlatformRename + accept() + + + 257 + 71 + + + 149 + 44 + + + + diff --git a/gui/output_window.ui b/gui/output_window.ui index 3ba39c9..50c50d1 100644 --- a/gui/output_window.ui +++ b/gui/output_window.ui @@ -1,104 +1,37 @@ - ResultsWindow - + OutputWindow + 0 0 - 400 - 355 + 1330 + 630 - - - 400 - 355 - - - - - 400 - 355 - - Results - + - 10 - 10 - 381 - 301 + -10 + -10 + 1350 + 650 - - - 11 - - - - - - - 318 - 323 - 75 - 24 - - - - - 10 - - - - OK - - - - - - 238 - 323 - 75 - 24 - - - - - 10 - - - - Save - - - output - res_save_but - res_ok_but - + + + MplWidget + QWidget +
widgets/mpl_widget
+ 1 +
+
- - - res_ok_but - clicked(bool) - ResultsWindow - close() - - - 357 - 281 - - - 199 - 149 - - - - +
diff --git a/gui/platform_edit.ui b/gui/platform_edit.ui index fe3a57d..56f5558 100644 --- a/gui/platform_edit.ui +++ b/gui/platform_edit.ui @@ -2,6 +2,9 @@ PlatformEdit + + Qt::WindowModality::ApplicationModal + 0 diff --git a/gui/platform_list.ui b/gui/platform_list.ui index fe6679a..e9d1222 100644 --- a/gui/platform_list.ui +++ b/gui/platform_list.ui @@ -2,6 +2,9 @@ PlatformList + + Qt::WindowModality::ApplicationModal + 0 @@ -120,5 +123,22 @@ - + + + plist_save_but + clicked() + PlatformList + close() + + + 196 + 453 + + + 131 + 236 + + + + diff --git a/src/db_handler.py b/src/db_handler.py index 7e95c14..252d62e 100644 --- a/src/db_handler.py +++ b/src/db_handler.py @@ -1,11 +1,13 @@ import os import sqlite3 -import data_struct + +import resource_finder from data_struct import Platform class DBHandler: def __init__(self): + # Function to create all necessary database tables if the DB file doesn't exist def create_tables(): self.cur.execute(""" CREATE TABLE "tblPlatforms" ( @@ -59,16 +61,17 @@ class DBHandler: ) """) - if not os.path.exists("SIPPCompare.db"): + if not os.path.exists(resource_finder.get_res_path("SIPPCompare.db")): db_exists = False else: db_exists = True - self.conn = sqlite3.connect("SIPPCompare.db") + self.conn = sqlite3.connect(resource_finder.get_res_path("SIPPCompare.db")) self.cur = self.conn.cursor() if not db_exists: create_tables() + # Retrieve the list of platform names for list population def retrieve_plat_list(self) -> list: res = self.cur.execute("SELECT PlatformName FROM tblPlatforms").fetchall() plat_name_list = [] @@ -77,7 +80,80 @@ class DBHandler: return plat_name_list + def write_platforms(self, plat_list: list[Platform]): + for i in range(len(plat_list)): + platforms_data = [ + plat_list[i].plat_id, + plat_list[i].plat_name, + plat_list[i].enabled + ] + + flat_plat_fees_data = [ + plat_list[i].plat_id, + plat_list[i].share_plat_fee, + plat_list[i].share_plat_max_fee + ] + + flat_deal_fees_data = [ + plat_list[i].plat_id, + plat_list[i].fund_deal_fee, + plat_list[i].share_deal_fee, + plat_list[i].share_deal_reduce_trades, + plat_list[i].share_deal_reduce_amount + ] + + res = self.cur.execute(f""" + SELECT EXISTS( + SELECT PlatformID FROM tblPlatforms + WHERE PlatformID = {i} + )""").fetchall() + + if res[0][0] == 1: + self.cur.execute(f""" + UPDATE tblPlatforms SET + PlatformID = ?, + PlatformName = ?, + IsEnabled = ? + WHERE PlatformID = {i} + """, platforms_data) + + self.cur.execute(f""" + UPDATE tblFlatPlatFees SET + PlatformID = ?, + SharePlatFee = ?, + SharePlatMaxFee = ? + WHERE PlatformID = {i} + """, flat_plat_fees_data) + + self.cur.execute(f""" + UPDATE tblFlatDealFees SET + PlatformID = ?, + FundDealFee = ?, + ShareDealFee = ?, + ShareDealReduceTrades = ?, + ShareDealReduceAmount = ? + WHERE PlatformID = {i} + """, flat_deal_fees_data) + + self.cur.execute(f"DELETE FROM tblFundPlatFee WHERE PlatformID = {i}") + else: + self.cur.execute("INSERT INTO tblPlatforms VALUES (?, ?, ?)", platforms_data) + self.cur.execute("INSERT INTO tblFlatPlatFees VALUES (?, ?, ?)", flat_plat_fees_data) + self.cur.execute("INSERT INTO tblFlatDealFees VALUES (?, ?, ?, ?, ?)", flat_deal_fees_data) + + exec_str = f"INSERT INTO tblFundPlatFee VALUES\n" + for x in range(len(plat_list[i].fund_plat_fee[0])): + band = plat_list[i].fund_plat_fee[0][x] + fee = plat_list[i].fund_plat_fee[1][x] + exec_str += f"({i}, {band}, {fee}),\n" + exec_str = exec_str[:-2] + self.cur.execute(exec_str) + + self.conn.commit() + + # Retrieve all info about all platforms in DB and initialise Platform objects def retrieve_platforms(self) -> list[Platform]: + # Retrieve all one-to-one relations platforms_res = self.cur.execute(""" SELECT -- tblPlatforms @@ -104,10 +180,13 @@ class DBHandler: platform[5], platform[6], platform[7], platform[8]] platforms.append(this_platform) + # Insert 2D array into each platform data in preparation for fund_plat_fee retrival for platform in platforms: platform.insert(1, [[], []]) - fund_plat_fee_res = self.cur.execute("SELECT * FROM tblFundPlatFee").fetchall() + # Get all records from tblFundPlatFee, add them to the platforms list based on ID + # WARNING: This code is dependent on PlatformID being sequential from 0 in DB records + fund_plat_fee_res = self.cur.execute("SELECT * FROM tblFundPlatFee ORDER BY PlatformID ASC").fetchall() for i in range(len(fund_plat_fee_res)): plat_id = fund_plat_fee_res[i][0] platforms[plat_id][1][0].append(fund_plat_fee_res[i][1]) @@ -123,15 +202,18 @@ class DBHandler: return platform_obj_list - + # This function writes the details the user entered this session to the DB def write_user_details(self, pension_val: float, slider_val: int, share_trades: int, fund_trades: int): # Hardcode UserID as 0 user_details_data = (0, pension_val, slider_val, share_trades, fund_trades) + # Check if there is already a record in tblUserDetails res = self.cur.execute("SELECT EXISTS(SELECT 1 FROM tblUserDetails)").fetchone() if res[0] == 0: + # If there isn't then insert a new record self.cur.execute("INSERT INTO tblUserDetails VALUES (?, ?, ?, ?, ?)", user_details_data) else: + # If there is then update the existing record (only ever one record as of now) self.cur.execute(""" UPDATE tblUserDetails SET UserID = ?, @@ -142,6 +224,7 @@ class DBHandler: """, user_details_data) self.conn.commit() + # Function to retrieve details entered by the user in prev session from DB def retrieve_user_details(self) -> dict: res = self.cur.execute("SELECT EXISTS(SELECT 1 FROM tblUserDetails)").fetchone() if res[0] == 0: diff --git a/src/main.py b/src/main.py index 4ee5f19..86dad41 100644 --- a/src/main.py +++ b/src/main.py @@ -1,23 +1,12 @@ -from PyQt6.QtWidgets import QApplication - import sys -import platform_edit -import main_window +from PyQt6.QtWidgets import QApplication + +from platform_edit import PlatformEdit +from main_window import SIPPCompare app = QApplication(sys.argv) -# Show platform edit window first, before main win -# 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)""" -#plat_edit_win = platform_edit.PlatformEdit() -window = main_window.SIPPCompare() - +window = SIPPCompare() window.show() app.exec() diff --git a/src/main_window.py b/src/main_window.py index f500469..8bf91de 100644 --- a/src/main_window.py +++ b/src/main_window.py @@ -1,17 +1,18 @@ -from PyQt6.QtGui import QIntValidator, QIcon -from PyQt6.QtWidgets import QMainWindow, QWidget from PyQt6 import uic +from PyQt6.QtGui import QIntValidator, QIcon +from PyQt6.QtWidgets import QMainWindow -import output_window -import platform_list import resource_finder -import db_handler +from db_handler import DBHandler +from output_window import OutputWindow +from platform_list import PlatformList class SIPPCompare(QMainWindow): def __init__(self): super().__init__() # Import Qt Designer UI XML file + uic.loadUi(resource_finder.get_res_path("gui/main_gui.ui"), self) self.setWindowIcon(QIcon(resource_finder.get_res_path("icon2.ico"))) @@ -32,11 +33,12 @@ class SIPPCompare(QMainWindow): self.fund_deal_fees = 0.0 self.share_plat_fees = 0.0 self.share_deal_fees = 0.0 + self.results = [] # Create window objects - self.db = db_handler.DBHandler() - self.platform_list_win = platform_list.PlatformList(self.db) - self.output_win = output_window.OutputWindow() + self.db = DBHandler() + self.platform_list_win = PlatformList(self.db) + self.output_win = OutputWindow() # Handle events self.calc_but.clicked.connect(self.calculate_fees) @@ -61,8 +63,6 @@ class SIPPCompare(QMainWindow): self.fund_trades_combo.setCurrentText(str(prev_session_data["fund_trades"])) self.calc_but.setFocus() - - # Display slider position as mix between two nums (funds/shares) def update_slider_lab(self): slider_val = self.mix_slider.value() @@ -78,6 +78,7 @@ class SIPPCompare(QMainWindow): self.calc_but.setEnabled(False) # Get variables from platform editor input fields + """ def init_variables(self): self.optional_boxes = self.platform_win.get_optional_boxes() self.fund_plat_fee = self.platform_win.get_fund_plat_fee() @@ -109,50 +110,59 @@ class SIPPCompare(QMainWindow): self.share_deal_reduce_amount = self.platform_win.get_share_deal_reduce_amount() else: self.share_deal_reduce_amount = None + """ + def init_variables(self): + self.fund_plat_fee = 1 # Calculate fees def calculate_fees(self): - self.init_variables() - - # Set to zero each time to avoid persistence - self.fund_plat_fees = 0 + # Set to empty list each time to avoid persistence + self.results = [] # Get user input value_num = float(self.value_input.value()) slider_val: int = self.mix_slider.value() fund_trades_num = int(self.fund_trades_combo.currentText()) share_trades_num = int(self.share_trades_combo.currentText()) - - # Funds/shares mix funds_value = (slider_val / 100) * value_num - if self.fund_deal_fee is not None: - self.fund_deal_fees = fund_trades_num * self.fund_deal_fee - - for i in range(1, len(self.fund_plat_fee[0])): - band = self.fund_plat_fee[0][i] - prev_band = self.fund_plat_fee[0][i - 1] - fee = self.fund_plat_fee[1][i] - gap = (band - prev_band) - - if funds_value > gap: - self.fund_plat_fees += gap * (fee / 100) - funds_value -= gap - else: - self.fund_plat_fees += funds_value * (fee / 100) - break - shares_value = (1 - (slider_val / 100)) * value_num - if self.share_plat_max_fee is not None: - if (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 - if self.share_deal_reduce_trades is not None: - if (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 + for platform in self.platform_list_win.plat_list: + fund_plat_fees = 0.0 + fund_deal_fees = 0.0 + share_plat_fees = 0.0 + share_deal_fees = 0.0 + plat_name = platform.plat_name + + if platform.fund_deal_fee is not None: + fund_deal_fees = fund_trades_num * platform.fund_deal_fee + + for i in range(1, len(platform.fund_plat_fee[0])): + band = platform.fund_plat_fee[0][i] + prev_band = platform.fund_plat_fee[0][i - 1] + fee = platform.fund_plat_fee[1][i] + gap = (band - prev_band) + + if funds_value > gap: + fund_plat_fees += gap * (fee / 100) + funds_value -= gap + else: + fund_plat_fees += funds_value * (fee / 100) + break + + if platform.share_plat_max_fee is not None: + if (platform.share_plat_fee * shares_value / 12) > platform.share_plat_max_fee: + share_plat_fees = platform.share_plat_max_fee * 12 + else: + share_plat_fees = platform.share_plat_fee * shares_value + + if platform.share_deal_reduce_trades is not None: + if (share_trades_num / 12) >= platform.share_deal_reduce_trades: + share_deal_fees = platform.share_deal_reduce_amount * share_trades_num + else: + share_deal_fees = platform.share_deal_fee * share_trades_num + + self.results.append([fund_plat_fees, fund_deal_fees, share_plat_fees, share_deal_fees, plat_name]) self.db.write_user_details(value_num, slider_val, share_trades_num, fund_trades_num) self.show_output_win() @@ -160,10 +170,7 @@ class SIPPCompare(QMainWindow): # Show the output window - this func is called from calculate_fee() 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.output_win.display_output(self.results) self.output_win.show() def show_platform_list(self): diff --git a/src/output_window.py b/src/output_window.py index 06933da..fe80f64 100644 --- a/src/output_window.py +++ b/src/output_window.py @@ -1,10 +1,10 @@ -from PyQt6.QtGui import QIcon -from PyQt6.QtWidgets import QWidget -from PyQt6 import uic - import datetime import os +from PyQt6 import uic +from PyQt6.QtGui import QIcon +from PyQt6.QtWidgets import QWidget + import resource_finder @@ -15,6 +15,7 @@ class OutputWindow(QWidget): uic.loadUi(resource_finder.get_res_path("gui/output_window.ui"), self) self.setWindowIcon(QIcon(resource_finder.get_res_path("icon2.ico"))) + """ # Initialise class variables self.results_str = "" self.platform_name = "" @@ -62,3 +63,25 @@ class OutputWindow(QWidget): self.results_str += f"\n\nTotal fees: £{round(total_fees, 2):.2f}" self.output.setText(self.results_str) + """ + + def display_output(self, results: list): + self.graphWidget.canvas.axes.clear() + self.graphWidget.canvas.axes.cla() + self.graphWidget.canvas.draw_idle() + ax = self.graphWidget.canvas.axes + #self.graphWidget.clf() + names = [] + values = [] + for result in results: + names.append(result[4]) + values.append(sum(result[:4])) + h_bars = ax.barh(names, values) + #labels = [] + #for value in values: + # labels.append(f"£{str(value)}") + + ax.bar_label(h_bars, label_type='center', labels=[f"£{x:,.2f}" for x in h_bars.datavalues]) + #ax.draw() + #self.graphWidget.draw() + diff --git a/src/platform_edit.py b/src/platform_edit.py index 07b8817..cf45221 100644 --- a/src/platform_edit.py +++ b/src/platform_edit.py @@ -1,12 +1,12 @@ -from PyQt6.QtCore import QRegularExpression +from PyQt6 import uic +from PyQt6.QtCore import QRegularExpression, QRect from PyQt6.QtGui import QRegularExpressionValidator, QFont, QIcon from PyQt6.QtWidgets import QWidget, QLabel -from PyQt6 import uic -from widgets.fastedit_spinbox import FastEditQDoubleSpinBox -from data_struct import Platform -import main_window import resource_finder +from db_handler import DBHandler +from data_struct import Platform +from widgets.fastedit_spinbox import FastEditQDoubleSpinBox class PlatformEdit(QWidget): @@ -20,14 +20,10 @@ class PlatformEdit(QWidget): self.plat = plat self.fund_plat_fee = self.plat.fund_plat_fee self.widgets_list_list = [] - - self.fund_fee_rows = len(self.plat.fund_plat_fee[0]) - """ - # Debugging feature: set with "--DEBUG_AUTOFILL" cmd argument - self.autofill = autofill - if autofill: - self.save_but.setEnabled(True) - """ + if len(self.plat.fund_plat_fee[0]) > 1: + self.fund_fee_rows = len(self.plat.fund_plat_fee[0]) - 1 + else: + self.fund_fee_rows = 1 self.required_fields = [ self.share_plat_fee_box, @@ -58,6 +54,7 @@ class PlatformEdit(QWidget): False ] + # Set optional checkboxes based on DB storage if self.plat.plat_name is None: self.check_boxes_ticked[0] = False self.plat_name_check.setChecked(False) @@ -102,8 +99,10 @@ class PlatformEdit(QWidget): self.share_deal_reduce_amount_check.setChecked(True) self.share_deal_reduce_amount_box.setValue(self.plat.share_deal_reduce_amount) - self.first_tier_box.setValue(self.plat.fund_plat_fee[0][1]) - self.first_tier_fee_box.setValue(self.plat.fund_plat_fee[1][1]) + # Populate fund platform fee rows from DB + if len(self.plat.fund_plat_fee[0]) > 1: + self.first_tier_box.setValue(self.plat.fund_plat_fee[0][1]) + self.first_tier_fee_box.setValue(self.plat.fund_plat_fee[1][1]) self.add_row(loading=True) # Handle events @@ -150,34 +149,34 @@ class PlatformEdit(QWidget): # Get fee structure variables from user input when "Save" clicked def init_variables(self): - """ - # If debugging, save time by hardcoding - if self.autofill: - self.plat_name = "AJBell" - self.fund_plat_fee = [ - [0, 250000, 1000000, 2000000], - [0, 0.25, 0.1, 0.05] - ] - 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 - self.check_boxes_ticked = [True, True, True, True, True] - else: - """ - self.plat_name = self.plat_name_box.text() - self.fund_plat_fee = self.create_plat_fee_struct() - 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()) + self.plat.fund_plat_fee = self.create_plat_fee_struct() + self.plat.share_plat_fee = float(self.share_plat_fee_box.value()) / 100 + self.plat.share_deal_fee = float(self.share_deal_fee_box.value()) - # Once user input is received show main window - self.main_win.show() + if self.check_boxes_ticked[0]: + self.plat.plat_name = self.plat_name_box.text() + else: + self.plat.plat_name = None + + if self.check_boxes_ticked[1]: + self.plat.fund_deal_fee = float(self.fund_deal_fee_box.value()) + else: + self.plat.fund_deal_fee = None + + if self.check_boxes_ticked[2]: + self.plat.share_plat_max_fee = float(self.share_plat_max_fee_box.value()) + else: + self.plat.share_plat_max_fee = None + + if self.check_boxes_ticked[3]: + self.plat.share_deal_reduce_trades = int(self.share_deal_reduce_trades_box.value()) + else: + self.plat.share_deal_reduce_trades = None + + if self.check_boxes_ticked[4]: + self.plat.share_deal_reduce_amount = float(self.share_deal_reduce_amount_box.value()) + else: + self.plat.share_deal_reduce_amount = None # This method does multiple things in order to validate the user's inputs: # 1) Check all required fields have a non-zero value @@ -261,12 +260,12 @@ class PlatformEdit(QWidget): def add_row(self, loading: bool = False): if loading: - rows_needed = self.fund_fee_rows + rows_needed = self.fund_fee_rows - 1 else: rows_needed = 1 - widgets = [] for x in range(rows_needed): + widgets = [] font = QFont() font.setPointSize(11) @@ -279,7 +278,7 @@ class PlatformEdit(QWidget): widgets[1].setButtonSymbols(FastEditQDoubleSpinBox.ButtonSymbols.NoButtons) widgets[1].setFont(font) if loading: - widgets[1].setValue(self.plat.fund_plat_fee[0][x+1]) + widgets[1].setValue(self.plat.fund_plat_fee[0][x+2]) widgets[1].valueChanged.connect(self.check_valid) widgets[1].valueChanged.connect(self.update_tier_labels) @@ -293,13 +292,20 @@ class PlatformEdit(QWidget): widgets[3].setButtonSymbols(FastEditQDoubleSpinBox.ButtonSymbols.NoButtons) widgets[3].setFont(font) if loading: - widgets[3].setValue(self.plat.fund_plat_fee[1][x+1]) + widgets[3].setValue(self.plat.fund_plat_fee[1][x+2]) widgets[3].valueChanged.connect(self.check_valid) # TODO: why 28.5? - self.gridLayoutWidget_2.setGeometry(11, 309, 611, int(round(28.5 * (self.fund_fee_rows + 1), 0))) + if loading: + grid_height = int(round(28.5 * self.fund_fee_rows)) + else: + grid_height = int(round(28.5 * (self.fund_fee_rows + 1))) + self.gridLayoutWidget_2.setGeometry(QRect(11, 309, 611, grid_height)) for i in range(len(widgets)): - self.gridLayout_2.addWidget(widgets[i], self.fund_fee_rows, i, 1, 1) + if loading: + self.gridLayout_2.addWidget(widgets[i], x + 1, i, 1, 1) + else: + self.gridLayout_2.addWidget(widgets[i], self.fund_fee_rows, i, 1, 1) if not loading: self.fund_fee_rows += 1 @@ -324,6 +330,7 @@ class PlatformEdit(QWidget): self.add_row_but.setEnabled(False) self.check_valid() + self.update_tier_labels() # TODO: Tab order diff --git a/src/platform_list.py b/src/platform_list.py index 110f0a9..93e8f9c 100644 --- a/src/platform_list.py +++ b/src/platform_list.py @@ -1,56 +1,94 @@ -from PyQt6.QtWidgets import QWidget, QListWidgetItem -from PyQt6.QtGui import QIcon, QRegularExpressionValidator -from PyQt6.QtCore import QRegularExpression from PyQt6 import uic +from PyQt6.QtCore import QRegularExpression +from PyQt6.QtGui import QIcon, QRegularExpressionValidator +from PyQt6.QtWidgets import QWidget, QListWidgetItem, QDialog import resource_finder -import data_struct -import platform_edit +from db_handler import DBHandler +from data_struct import Platform +from platform_edit import PlatformEdit -class PlatformRename(QWidget): - def __init__(self): - super().__init__() +class PlatformRename(QDialog): + def __init__(self, parent=None): + super().__init__(parent) # Import Qt Designer UI XML file uic.loadUi(resource_finder.get_res_path("gui/dialogs/platform_rename.ui"), self) self.setWindowIcon(QIcon(resource_finder.get_res_path("icon2.ico"))) + self.rename_plat_box.setFocus() + self.new_name = "" + # Set validators # Regex accepts any characters that match [a-Z], [0-9] or _ self.rename_plat_box.setValidator( QRegularExpressionValidator(QRegularExpression("\\w*")) ) + self.rename_plat_ok_but.clicked.connect(self.store_new_name) + + def store_new_name(self): + self.new_name = self.rename_plat_box.text() + + def closeEvent(self, event): + event.ignore() + self.reject() + class PlatformList(QWidget): - def __init__(self, db): + def __init__(self, db: DBHandler): super().__init__() # Import Qt Designer UI XML file uic.loadUi(resource_finder.get_res_path("gui/platform_list.ui"), self) self.setWindowIcon(QIcon(resource_finder.get_res_path("icon2.ico"))) - self.plat_list_dialog = PlatformRename() - self.p_edit = None self.db = db - self.plat_name_list = self.db.retrieve_plat_list() - self.plat_list = self.db.retrieve_platforms() - print(self.plat_list[1].fund_plat_fee) - print(self.plat_name_list) + self.plat_edit_win = None + self.plat_list_dialog = PlatformRename() + self.plat_list = [] + self.plat_name_list = [] + self.new_plat_name = "" + self.update_plat_list() - for platform in self.plat_name_list: + for i in range(len(self.plat_name_list)): + plat_name = self.plat_name_list[i] item = QListWidgetItem() - item.setText(platform) + if plat_name is not None: + item.setText(plat_name) + else: + item.setText(f"Unnamed [ID: {i}]") + self.platListWidget.addItem(item) # Handle events self.add_plat_but.clicked.connect(self.add_platform) self.del_plat_but.clicked.connect(self.remove_platform) self.edit_plat_but.clicked.connect(self.edit_platform) + self.plist_save_but.clicked.connect(self.save_platforms) self.plat_enabled_check.checkStateChanged.connect(self.toggle_platform_state) self.platListWidget.currentRowChanged.connect(self.get_enabled_state) + def update_plat_list(self): + self.plat_name_list = self.db.retrieve_plat_list() + self.plat_list = self.db.retrieve_platforms() + def add_platform(self): - self.plat_list_dialog.show() + name_dialog_res = self.plat_list_dialog.exec() + if name_dialog_res == QDialog.DialogCode.Accepted: + name = self.plat_list_dialog.new_name + index = self.platListWidget.count() + if name != "": + self.platListWidget.addItem(name) + name_param = name + else: + self.platListWidget.addItem(f"Unnamed [ID: {index}]") + name_param = None + + self.plat_list.append(Platform( + index, [[0], [0]], name_param, True, 0, 0, None, 0, None, None) + ) + self.plat_edit_win = PlatformEdit(self.plat_list[index]) + self.plat_edit_win.show() def get_enabled_state(self): index = self.platListWidget.currentRow() @@ -62,8 +100,11 @@ class PlatformList(QWidget): def edit_platform(self): index = self.platListWidget.currentRow() - self.p_edit = platform_edit.PlatformEdit(self.plat_list[index]) - self.p_edit.show() + self.plat_edit_win = PlatformEdit(self.plat_list[index]) + self.plat_edit_win.show() + + def save_platforms(self): + self.db.write_platforms(self.plat_list) def toggle_platform_state(self): return None diff --git a/src/widgets/mpl_widget.py b/src/widgets/mpl_widget.py new file mode 100644 index 0000000..1d6e664 --- /dev/null +++ b/src/widgets/mpl_widget.py @@ -0,0 +1,14 @@ +from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg +from matplotlib.figure import Figure +from PyQt6.QtWidgets import QWidget, QVBoxLayout + + +class MplWidget(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + self.canvas = FigureCanvasQTAgg(Figure(figsize=(10, 10), dpi=100)) + vertical_layout = QVBoxLayout() + #vertical_layout.setSpacing(0) + vertical_layout.addWidget(self.canvas) + self.canvas.axes = self.canvas.figure.add_subplot(1, 1, 1) + self.setLayout(vertical_layout) \ No newline at end of file