Finish reorganization of ui code

This commit is contained in:
Andreas Tsouchlos 2024-01-04 21:08:40 +01:00
parent 050f5f0ae4
commit ab1a1c3aad
2 changed files with 205 additions and 180 deletions

View File

@ -0,0 +1,178 @@
import pandas as pd
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QPixmap, QAction
from PyQt6.QtWidgets import QHBoxLayout, QLabel, QMenu, QInputDialog, \
QMessageBox
from banking_breakdown.ui.pandas_model import PandasModel
class WarningItem(QHBoxLayout):
"""Item appearing at top of Window with warning icon."""
def __init__(self, text: str, parent=None):
super(WarningItem, self).__init__()
self._warningIcon = QLabel()
pixmap = QPixmap("res/warning.png")
self._warningIcon.setPixmap(pixmap)
self._label = QLabel(text)
self._label.setWordWrap(True)
self.addWidget(self._warningIcon)
self.addWidget(self._label)
self.setStretch(0, 0)
self.setStretch(1, 1)
def hide(self):
self._label.hide()
self._warningIcon.hide()
class HeaderContextMenu(QMenu):
"""Context menu appearing when right-clicking the header of the QTableView.
"""
def __init__(self, column, pandas_model: PandasModel, callback=None,
parent=None):
super(HeaderContextMenu, self).__init__()
self._column = column
self._pandas_model = pandas_model
self._callback = callback
self._column_text \
= self._pandas_model.headerData(self._column,
Qt.Orientation.Horizontal)
# Define assign action
assign_menu = QMenu("Assign type", self)
assign_date_action = QAction("date", self)
assign_float_action = QAction("float", self)
assign_menu.addAction(assign_date_action)
assign_menu.addAction(assign_float_action)
assign_date_action.triggered.connect(self._assign_date_handler)
assign_float_action.triggered.connect(self._assign_float_handler)
# Define other actions
rename_action = QAction("Rename", self)
delete_action = QAction("Delete", self)
switch_action = QAction("Switch position", self)
rename_action.triggered.connect(self._rename_handler)
delete_action.triggered.connect(self._delete_handler)
switch_action.triggered.connect(self._switch_handler)
# Add actions to menu
self.addAction(rename_action)
self.addAction(delete_action)
self.addAction(switch_action)
self.addAction(assign_menu.menuAction())
def _rename_handler(self):
new_name, flag = QInputDialog.getText(self, "Rename column",
"New name:",
text=self._column_text)
if not flag:
return
if (new_name != self._column_text) and (new_name != ''):
df = self._pandas_model.get_dataframe()
df = df.rename(columns={self._column_text: new_name})
self._pandas_model.set_dataframe(df)
if self._callback:
self._callback()
def _delete_handler(self):
button = QMessageBox.question(self, "Delete column",
f"Are you sure you want to delete"
f" column '{self._column_text}'?")
if button == QMessageBox.StandardButton.Yes:
df = self._pandas_model.get_dataframe()
df = df.iloc[:, [j for j, c
in enumerate(df.columns) if j != self._column]]
self._pandas_model.set_dataframe(df)
if self._callback:
self._callback()
def _switch_handler(self):
df = self._pandas_model.get_dataframe()
columns = [column for column in df.columns
if column != self._column_text]
other_name, flag = QInputDialog.getItem(self, "Switch column position",
f"Switch position of colum"
f" '{self._column_text}' with:",
columns, editable=False)
if not flag:
return
column_titles = list(df.columns)
index1, index2 = column_titles.index(
self._column_text), column_titles.index(other_name)
column_titles[index1], column_titles[index2] \
= column_titles[index2], column_titles[index1]
df = df.reindex(columns=column_titles)
self._pandas_model.set_dataframe(df)
if self._callback:
self._callback()
def _assign_date_handler(self):
date_format, flag = QInputDialog.getText(self, "Format",
"Format:",
text="%d.%m.%Y")
if not flag:
return
df = self._pandas_model.get_dataframe()
try:
df[self._column_text] \
= pd.to_datetime(df[self._column_text], format=date_format)
except:
QMessageBox.warning(self, "No action performed",
"An error occurred.")
self._pandas_model.set_dataframe(df)
if self._callback:
self._callback()
def _assign_float_handler(self):
chars = ['.', ',']
decimal_sep, flag = QInputDialog.getItem(self, "Decimal separator",
"Decimal separator:",
chars, editable=False)
if not flag:
return
df = self._pandas_model.get_dataframe()
try:
if decimal_sep == ',':
df[self._column_text] \
= df[self._column_text].str.replace(',', '.').astype(float)
else:
df[self._column_text] = df[self._column_text].astype(float)
except:
QMessageBox.warning(self, "No action performed",
"An error occurred.")
self._pandas_model.set_dataframe(df)
if self._callback:
self._callback()

View File

@ -1,181 +1,13 @@
import typing
from functools import partial
import pandas as pd
from PyQt6 import uic
from PyQt6.QtCore import Qt, QSortFilterProxyModel
from PyQt6.QtGui import QPixmap, QAction
from PyQt6.QtWidgets import QMainWindow, QPushButton, QHBoxLayout, QLabel, \
QVBoxLayout, QMenu, QTableView, QInputDialog, QMessageBox, QFileDialog, \
QListWidget
from PyQt6.QtGui import QAction
from PyQt6.QtWidgets import QMainWindow, QPushButton, QVBoxLayout, \
QTableView, QInputDialog, QMessageBox, QFileDialog, QListWidget
from banking_breakdown.ui.pandas_model import PandasModel
class WarningItem(QHBoxLayout):
def __init__(self, text: str, parent=None):
super(WarningItem, self).__init__()
self._warningIcon = QLabel()
pixmap = QPixmap("res/warning.png")
self._warningIcon.setPixmap(pixmap)
self._label = QLabel(text)
self._label.setWordWrap(True)
self.addWidget(self._warningIcon)
self.addWidget(self._label)
self.setStretch(0, 0)
self.setStretch(1, 1)
def hide(self):
self._label.hide()
self._warningIcon.hide()
class HeaderContextMenu(QMenu):
def __init__(self, column, pandas_model: PandasModel, callback=None,
parent=None):
super(HeaderContextMenu, self).__init__()
self._column = column
self._pandas_model = pandas_model
self._callback = callback
self._column_text \
= self._pandas_model.headerData(self._column,
Qt.Orientation.Horizontal)
# Define assign action
assign_menu = QMenu("Assign type", self)
assign_date_action = QAction("date", self)
assign_float_action = QAction("float", self)
assign_menu.addAction(assign_date_action)
assign_menu.addAction(assign_float_action)
assign_date_action.triggered.connect(self._assign_date_handler)
assign_float_action.triggered.connect(self._assign_float_handler)
# Define other actions
rename_action = QAction("Rename", self)
delete_action = QAction("Delete", self)
switch_action = QAction("Switch position", self)
rename_action.triggered.connect(self._rename_handler)
delete_action.triggered.connect(self._delete_handler)
switch_action.triggered.connect(self._switch_handler)
# Add actions to menu
self.addAction(rename_action)
self.addAction(delete_action)
self.addAction(switch_action)
self.addAction(assign_menu.menuAction())
def _rename_handler(self):
new_name, flag = QInputDialog.getText(self, "Rename column",
"New name:",
text=self._column_text)
if not flag:
return
if (new_name != self._column_text) and (new_name != ''):
df = self._pandas_model.get_dataframe()
df = df.rename(columns={self._column_text: new_name})
self._pandas_model.set_dataframe(df)
if self._callback:
self._callback()
def _delete_handler(self):
button = QMessageBox.question(self, "Delete column",
f"Are you sure you want to delete"
f" column '{self._column_text}'?")
if button == QMessageBox.StandardButton.Yes:
df = self._pandas_model.get_dataframe()
df = df.iloc[:, [j for j, c
in enumerate(df.columns) if j != self._column]]
self._pandas_model.set_dataframe(df)
if self._callback:
self._callback()
def _switch_handler(self):
df = self._pandas_model.get_dataframe()
columns = [column for column in df.columns
if column != self._column_text]
other_name, flag = QInputDialog.getItem(self, "Switch column position",
f"Switch position of colum"
f" '{self._column_text}' with:",
columns, editable=False)
if not flag:
return
column_titles = list(df.columns)
index1, index2 = column_titles.index(
self._column_text), column_titles.index(other_name)
column_titles[index1], column_titles[index2] \
= column_titles[index2], column_titles[index1]
df = df.reindex(columns=column_titles)
self._pandas_model.set_dataframe(df)
if self._callback:
self._callback()
def _assign_date_handler(self):
date_format, flag = QInputDialog.getText(self, "Format",
"Format:",
text="%d.%m.%Y")
if not flag:
return
df = self._pandas_model.get_dataframe()
try:
df[self._column_text] \
= pd.to_datetime(df[self._column_text], format=date_format)
except:
QMessageBox.warning(self, "No action performed",
"An error occurred.")
self._pandas_model.set_dataframe(df)
if self._callback:
self._callback()
def _assign_float_handler(self):
chars = ['.', ',']
decimal_sep, flag = QInputDialog.getItem(self, "Decimal separator",
"Decimal separator:",
chars, editable=False)
if not flag:
return
df = self._pandas_model.get_dataframe()
try:
if decimal_sep == ',':
df[self._column_text] \
= df[self._column_text].str.replace(',', '.').astype(float)
else:
df[self._column_text] = df[self._column_text].astype(float)
except:
QMessageBox.warning(self, "No action performed",
"An error occurred.")
self._pandas_model.set_dataframe(df)
if self._callback:
self._callback()
from banking_breakdown.ui.custom_ui_items import WarningItem, HeaderContextMenu
class MainWindow(QMainWindow):
@ -226,7 +58,8 @@ class MainWindow(QMainWindow):
header = self._table_view.horizontalHeader()
header.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
header.customContextMenuRequested.connect(self._header_right_clicked)
header.customContextMenuRequested.connect(
self._handle_header_right_click)
self._list_widget.itemSelectionChanged.connect(
self._handle_list_selection_changed)
@ -234,18 +67,22 @@ class MainWindow(QMainWindow):
self._table_view.selectionModel().selectionChanged.connect(
self._handle_table_selection_changed)
# Table data updates
def set_statement_data(self, df: pd.DataFrame):
self._pandas_model.set_dataframe(df)
self._dataframe_update_callback()
self._resize_table_columns_to_content()
def get_statement_data(self) -> pd.DataFrame:
return self._pandas_model.get_dataframe()
def _dataframe_update_callback(self):
df = self._pandas_model.get_dataframe()
self._show_warnings()
self._update_categories_from_dataframe(df)
self._update_categories_from_dataframe()
def _resize_table_columns_to_content(self):
df = self._pandas_model.get_dataframe()
# Resize columns
@ -259,11 +96,15 @@ class MainWindow(QMainWindow):
len(col))
self._table_view.setColumnWidth(i, max_char * 10)
# List data updates
def _add_categories(self, categories: typing.Sequence[str]):
for category in categories:
self._list_widget.addItem(category)
def _update_categories_from_dataframe(self, df: pd.DataFrame):
def _update_categories_from_dataframe(self):
df = self._pandas_model.get_dataframe()
df_categories = df['category'].unique()
current_categories = [self._list_widget.item(x).text() for x
in range(self._list_widget.count())]
@ -272,6 +113,8 @@ class MainWindow(QMainWindow):
self._add_categories([category for category
in missing if category != ' '])
# Warnings
def _add_warning_item(self, text: str):
warning_item = WarningItem(text=text, parent=self)
@ -300,7 +143,9 @@ class MainWindow(QMainWindow):
"The column 'balance' does not exist. Please rename the column"
" containing the balance after each transaction to 'balance'")
def _header_right_clicked(self, pos):
# Event handlers
def _handle_header_right_click(self, pos):
column = self._table_view.horizontalHeader().logicalIndexAt(pos)
context = HeaderContextMenu(parent=self, column=column,
@ -345,9 +190,9 @@ class MainWindow(QMainWindow):
row_indices = [self._table_view.model().mapToSource(index).row()
for index in indexes]
df = self.get_statement_data()
df = self._pandas_model.get_dataframe()
df.loc[row_indices, 'category'] = category
self.set_statement_data(df)
self._pandas_model.set_dataframe(df)
def _handle_apply_click(self):
category = self._list_widget.selectedItems()[0].text()
@ -365,6 +210,8 @@ class MainWindow(QMainWindow):
df = self.get_statement_data()
df.to_csv(filename, index=False)
# Enable / Disable buttons
def _check_enable_delete_button(self):
if len(self._list_widget.selectedItems()) > 0:
self._delete_button.setEnabled(True)