Finish reorganization of ui code
This commit is contained in:
parent
050f5f0ae4
commit
ab1a1c3aad
178
banking_breakdown/ui/custom_ui_items.py
Normal file
178
banking_breakdown/ui/custom_ui_items.py
Normal 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()
|
||||
@ -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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user