diff --git a/.gitignore b/.gitignore
index 4689dc8..c439968 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
.idea/
__pycache__/
venv/
-build/
\ No newline at end of file
+build/
+banking_breakdown.egg-info
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..e7714ae
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,6 @@
+include banking_breakdown/ui/main_window.ui
+include banking_breakdown/ui/warning.png
+include banking_breakdown/document_builder/.latexmkrc
+include banking_breakdown/document_builder/Dockerfile.alpine
+include banking_breakdown/document_builder/report.tex
+include banking_breakdown/document_builder/common.tex
diff --git a/banking_breakdown/__main__.py b/banking_breakdown/__main__.py
index 4ca743c..8f52710 100644
--- a/banking_breakdown/__main__.py
+++ b/banking_breakdown/__main__.py
@@ -20,7 +20,7 @@ def categorize_func(args):
def report_func(args):
report_data = statement_parser.parse_statement(args.i)
- document_builder.build_document(report_data)
+ document_builder.build_document(args.o, report_data)
#
@@ -48,7 +48,10 @@ def main():
report_parser = subparsers.add_parser("report")
report_parser.set_defaults(func=report_func)
report_parser.add_argument('-i', required=True,
- help="CSV output file from categorization step")
+ help="CSV file containing bank statement data (output file of categorization step)")
+ report_parser.add_argument('-o', required=False,
+ help="Output directory ('build' by default)",
+ default='build')
args = parser.parse_args()
diff --git a/banking_breakdown/document_builder.py b/banking_breakdown/document_builder.py
deleted file mode 100644
index 7bf14fc..0000000
--- a/banking_breakdown/document_builder.py
+++ /dev/null
@@ -1,38 +0,0 @@
-import subprocess
-import os
-import shutil
-from banking_breakdown import types
-
-
-def _copy_resources():
- os.makedirs(os.path.dirname("build/report.tex"), exist_ok=True)
- shutil.copyfile("res/report.tex", "build/report.tex")
- shutil.copyfile("res/.latexmkrc", "build/.latexmkrc")
-
-
-def _serialize_report_data(report_data: types.ReportData):
- report_data.net_income.to_csv('build/net_income.csv', index=False)
- report_data.category_overview.to_csv('build/category_overview.csv',
- index=False)
- report_data.expenses_by_category.to_csv('build/expenses_by_category.csv',
- index=False)
- report_data.total_value.to_csv('build/total_value.csv', index=False)
- report_data.detailed_balance.to_csv('build/detailed_balance.csv',
- index=False)
-
-
-def _compile_document():
- subprocess.call("docker build . -f res/Dockerfile.alpine"
- " -t banking-breakdown",
- shell=True)
- subprocess.call("docker run --rm -u `id -u`:`id -g`"
- " -v $(realpath .):/project"
- " -w /project/build banking-breakdown"
- " latexmk -pdf report.tex",
- shell=True)
-
-
-def build_document(report_data: types.ReportData):
- _copy_resources()
- _serialize_report_data(report_data)
- _compile_document()
diff --git a/res/.latexmkrc b/banking_breakdown/document_builder/.latexmkrc
similarity index 100%
rename from res/.latexmkrc
rename to banking_breakdown/document_builder/.latexmkrc
diff --git a/res/Dockerfile.alpine b/banking_breakdown/document_builder/Dockerfile.alpine
similarity index 100%
rename from res/Dockerfile.alpine
rename to banking_breakdown/document_builder/Dockerfile.alpine
diff --git a/banking_breakdown/document_builder/__init__.py b/banking_breakdown/document_builder/__init__.py
new file mode 100644
index 0000000..334939a
--- /dev/null
+++ b/banking_breakdown/document_builder/__init__.py
@@ -0,0 +1,48 @@
+import subprocess
+import os
+import shutil
+from banking_breakdown import types
+
+import banking_breakdown.document_builder
+
+from importlib import resources
+
+
+def _copy_resources(build_dir: str):
+ res_prefix = resources.files(banking_breakdown.document_builder)
+
+ os.makedirs(os.path.dirname(f"{build_dir}/report.tex"), exist_ok=True)
+
+ shutil.copyfile(f"{res_prefix}/report.tex", f"{build_dir}/report.tex")
+ shutil.copyfile(f"{res_prefix}/common.tex", f"{build_dir}/common.tex")
+ shutil.copyfile(f"{res_prefix}/.latexmkrc", f"{build_dir}/.latexmkrc")
+
+
+def _serialize_report_data(build_dir: str, report_data: types.ReportData):
+ report_data.net_income.to_csv(f'{build_dir}/net_income.csv', index=False)
+ report_data.category_overview.to_csv(f'{build_dir}/category_overview.csv',
+ index=False)
+ report_data.expenses_by_category.to_csv(f'{build_dir}/expenses_by_category.csv',
+ index=False)
+ report_data.total_value.to_csv(f'{build_dir}/total_value.csv', index=False)
+ report_data.detailed_balance.to_csv(f'{build_dir}/detailed_balance.csv',
+ index=False)
+
+
+def _compile_document(build_dir):
+ res_prefix = resources.files(banking_breakdown.document_builder)
+
+ subprocess.call(f"docker build . -f {res_prefix}/Dockerfile.alpine"
+ " -t banking-breakdown",
+ shell=True)
+ subprocess.call("docker run --rm -u `id -u`:`id -g`"
+ " -v $(realpath .):/project"
+ f" -w /project/{build_dir} banking-breakdown"
+ " latexmk -pdf report.tex",
+ shell=True)
+
+
+def build_document(build_dir: str, report_data: types.ReportData):
+ _copy_resources(build_dir)
+ _serialize_report_data(build_dir, report_data)
+ _compile_document(build_dir)
diff --git a/banking_breakdown/document_builder/common.tex b/banking_breakdown/document_builder/common.tex
new file mode 100644
index 0000000..92d169f
--- /dev/null
+++ b/banking_breakdown/document_builder/common.tex
@@ -0,0 +1,317 @@
+% Author: Andreas Tsouchlos
+%
+% Collection of useful commands and definitions
+%
+% ||====================================================================||
+% || WARNING ||
+% ||====================================================================||
+% || The following packages have to be included before using this file: ||
+% || amsmath ||
+% || pgfplots ||
+% ||====================================================================||
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% Math Symbols %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+\DeclareMathOperator*{\argmin}{\arg\!\min}
+\DeclareMathOperator*{\argmax}{\arg\!\max}
+\DeclareMathOperator\sign{sign}
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% Data Manipulation %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+%
+% Filters for Pgfplots
+% Source: https://tex.stackexchange.com/a/58563 (modified)
+%
+
+\pgfplotsset{
+ discard if/.style 2 args={
+ x filter/.append code={
+ \edef\tempa{\thisrow{#1}}
+ \edef\tempb{#2}
+ \ifx\tempa\tempb
+ \def\pgfmathresult{inf}
+ \fi
+ }
+ },
+ discard if not/.style 2 args={
+ x filter/.append code={
+ \edef\tempa{\thisrow{#1}}
+ \edef\tempb{#2}
+ \ifx\tempa\tempb
+ \else
+ \def\pgfmathresult{inf}
+ \fi
+ }
+ },
+ discard if gt/.style 2 args={
+ x filter/.append code={
+ \edef\tempa{\thisrow{#1}}
+ \edef\tempb{#2}
+ \ifdim\tempa pt > \tempb pt
+ \def\pgfmathresult{inf}
+ \fi
+ }
+ },
+ discard if lt/.style 2 args={
+ x filter/.append code={
+ \edef\tempa{\thisrow{#1}}
+ \edef\tempb{#2}
+ \ifdim\tempa pt < \tempb pt
+ \def\pgfmathresult{inf}
+ \fi
+ }
+ }
+}
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% Graphics & Plotting %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%
+% Colors
+%
+
+% KIT Colors
+
+\definecolor{kit-green100}{rgb}{0,.59,.51}
+\definecolor{kit-green70}{rgb}{.3,.71,.65}
+\definecolor{kit-green50}{rgb}{.50,.79,.75}
+\definecolor{kit-green30}{rgb}{.69,.87,.85}
+\definecolor{kit-green15}{rgb}{.85,.93,.93}
+\definecolor{KITgreen}{rgb}{0,.59,.51}
+
+\definecolor{KITpalegreen}{RGB}{130,190,60}
+\colorlet{kit-maigreen100}{KITpalegreen}
+\colorlet{kit-maigreen70}{KITpalegreen!70}
+\colorlet{kit-maigreen50}{KITpalegreen!50}
+\colorlet{kit-maigreen30}{KITpalegreen!30}
+\colorlet{kit-maigreen15}{KITpalegreen!15}
+
+\definecolor{KITblue}{rgb}{.27,.39,.66}
+\definecolor{kit-blue100}{rgb}{.27,.39,.67}
+\definecolor{kit-blue70}{rgb}{.49,.57,.76}
+\definecolor{kit-blue50}{rgb}{.64,.69,.83}
+\definecolor{kit-blue30}{rgb}{.78,.82,.9}
+\definecolor{kit-blue15}{rgb}{.89,.91,.95}
+
+\definecolor{KITyellow}{rgb}{.98,.89,0}
+\definecolor{kit-yellow100}{cmyk}{0,.05,1,0}
+\definecolor{kit-yellow70}{cmyk}{0,.035,.7,0}
+\definecolor{kit-yellow50}{cmyk}{0,.025,.5,0}
+\definecolor{kit-yellow30}{cmyk}{0,.015,.3,0}
+\definecolor{kit-yellow15}{cmyk}{0,.0075,.15,0}
+
+\definecolor{KITorange}{rgb}{.87,.60,.10}
+\definecolor{kit-orange100}{cmyk}{0,.45,1,0}
+\definecolor{kit-orange70}{cmyk}{0,.315,.7,0}
+\definecolor{kit-orange50}{cmyk}{0,.225,.5,0}
+\definecolor{kit-orange30}{cmyk}{0,.135,.3,0}
+\definecolor{kit-orange15}{cmyk}{0,.0675,.15,0}
+
+\definecolor{KITred}{rgb}{.63,.13,.13}
+\definecolor{kit-red100}{cmyk}{.25,1,1,0}
+\definecolor{kit-red70}{cmyk}{.175,.7,.7,0}
+\definecolor{kit-red50}{cmyk}{.125,.5,.5,0}
+\definecolor{kit-red30}{cmyk}{.075,.3,.3,0}
+\definecolor{kit-red15}{cmyk}{.0375,.15,.15,0}
+
+\definecolor{KITpurple}{RGB}{160,0,120}
+\colorlet{kit-purple100}{KITpurple}
+\colorlet{kit-purple70}{KITpurple!70}
+\colorlet{kit-purple50}{KITpurple!50}
+\colorlet{kit-purple30}{KITpurple!30}
+\colorlet{kit-purple15}{KITpurple!15}
+
+\definecolor{KITcyanblue}{RGB}{80,170,230}
+\colorlet{kit-cyanblue100}{KITcyanblue}
+\colorlet{kit-cyanblue70}{KITcyanblue!70}
+\colorlet{kit-cyanblue50}{KITcyanblue!50}
+\colorlet{kit-cyanblue30}{KITcyanblue!30}
+\colorlet{kit-cyanblue15}{KITcyanblue!15}
+
+% Matplotlib Colors
+
+\definecolor{Mpl1}{HTML}{1f77b4}
+\definecolor{Mpl2}{HTML}{ff7f0e}
+\definecolor{Mpl3}{HTML}{2ca02c}
+\definecolor{Mpl4}{HTML}{d62728}
+\definecolor{Mpl5}{HTML}{9467bd}
+\definecolor{Mpl6}{HTML}{8c564b}
+\definecolor{Mpl7}{HTML}{e377c2}
+\definecolor{Mpl8}{HTML}{7f7f7f}
+\definecolor{Mpl9}{HTML}{bcbd22}
+\definecolor{Mpl10}{HTML}{17becf}
+
+%
+% Color Schemes
+%
+
+% Define colormaps
+
+\pgfplotsset{
+ colormap={mako}{
+ rgb=(0.18195582, 0.11955283, 0.23136943)
+ rgb=(0.25307401, 0.23772973, 0.48316271)
+ rgb=(0.21607792, 0.39736958, 0.61948028)
+ rgb=(0.20344718, 0.56074869, 0.65649508)
+ rgb=(0.25187832, 0.71827158, 0.67872193)
+ rgb=(0.54578602, 0.8544913, 0.69848331)
+ },
+ colormap={rocket}{
+ rgb=(0.20973515, 0.09747934, 0.24238489)
+ rgb=(0.43860848, 0.12177004, 0.34119475)
+ rgb=(0.67824099, 0.09192342, 0.3504148)
+ rgb=(0.8833417, 0.19830556, 0.26014181)
+ rgb=(0.95381595, 0.46373781, 0.31769923)
+ rgb=(0.96516917, 0.70776351, 0.5606593)
+ },
+ colormap={cividis}{
+ rgb=(0.130669, 0.231458, 0.43284)
+ rgb=(0.298421, 0.332247, 0.423973)
+ rgb=(0.42512, 0.431334, 0.447692)
+ rgb=(0.555393, 0.537807, 0.471147)
+ rgb=(0.695985, 0.648334, 0.440072)
+ rgb=(0.849223, 0.771947, 0.359729)
+ },
+ colormap={cel}{
+ color=(KITred!90!black);
+ color=(kit-blue100);
+ color=(kit-green70);
+ color=(kit-yellow70!80!kit-orange70);
+ },
+ colormap={matplotlib}{
+ % Source: https://github.com/matplotlib/matplotlib/blob/e5a85f960b2d47eac371cff709b830d52c36d267/lib/matplotlib/_cm.py#L1114
+ rgb=(0.2298057, 0.298717966, 0.753683153)
+ rgb=(0.26623388, 0.353094838, 0.801466763)
+ rgb=(0.30386891, 0.406535296, 0.84495867 )
+ rgb=(0.342804478, 0.458757618, 0.883725899)
+ rgb=(0.38301334, 0.50941904, 0.917387822)
+ rgb=(0.424369608, 0.558148092, 0.945619588)
+ rgb=(0.46666708, 0.604562568, 0.968154911)
+ rgb=(0.509635204, 0.648280772, 0.98478814 )
+ rgb=(0.552953156, 0.688929332, 0.995375608)
+ rgb=(0.596262162, 0.726149107, 0.999836203)
+ rgb=(0.639176211, 0.759599947, 0.998151185)
+ rgb=(0.681291281, 0.788964712, 0.990363227)
+ rgb=(0.722193294, 0.813952739, 0.976574709)
+ rgb=(0.761464949, 0.834302879, 0.956945269)
+ rgb=(0.798691636, 0.849786142, 0.931688648)
+ rgb=(0.833466556, 0.860207984, 0.901068838)
+ rgb=(0.865395197, 0.86541021, 0.865395561)
+ rgb=(0.897787179, 0.848937047, 0.820880546)
+ rgb=(0.924127593, 0.827384882, 0.774508472)
+ rgb=(0.944468518, 0.800927443, 0.726736146)
+ rgb=(0.958852946, 0.769767752, 0.678007945)
+ rgb=(0.96732803, 0.734132809, 0.628751763)
+ rgb=(0.969954137, 0.694266682, 0.579375448)
+ rgb=(0.966811177, 0.650421156, 0.530263762)
+ rgb=(0.958003065, 0.602842431, 0.481775914)
+ rgb=(0.943660866, 0.551750968, 0.434243684)
+ rgb=(0.923944917, 0.49730856, 0.387970225)
+ rgb=(0.89904617, 0.439559467, 0.343229596)
+ rgb=(0.869186849, 0.378313092, 0.300267182)
+ rgb=(0.834620542, 0.312874446, 0.259301199)
+ rgb=(0.795631745, 0.24128379, 0.220525627)
+ rgb=(0.752534934, 0.157246067, 0.184115123)
+ rgb=(0.705673158, 0.01555616, 0.150232812)
+ }
+}
+
+% Define cycle lists
+
+\pgfplotscreateplotcyclelist{mako}{%
+ [samples of colormap={4} of mako]%
+}
+\pgfplotscreateplotcyclelist{rocket}{%
+ [samples of colormap={4} of rocket]%
+}
+\pgfplotscreateplotcyclelist{cividis}{%
+ [samples of colormap={4} of cividis]%
+}
+\pgfplotscreateplotcyclelist{viridis}{%
+ [samples of colormap={4} of viridis]%
+}
+\pgfplotscreateplotcyclelist{cel}{%
+ [samples of colormap={4} of cel]%
+}
+\pgfplotscreateplotcyclelist{matplotlib}{%
+ {Mpl1},{Mpl2},{Mpl3},{Mpl4}
+}
+
+% Define 'scolX' colors
+
+\makeatletter
+
+\def\extractcolormapcolor#1#2{%
+ \expandafter\pgfplotscolormapaccess\expandafter[\pgfplotspointmetatransformedrange]%
+ [1.0]%
+ {#2}%
+ {\pgfkeysvalueof{/pgfplots/colormap name}}%
+ \def\pgfplots@loc@TMPb{\pgfutil@definecolor{#1}{\csname pgfpl@cm@\pgfkeysvalueof{/pgfplots/colormap name}@colspace\endcsname}}%
+ \expandafter\pgfplots@loc@TMPb\expandafter{\pgfmathresult}%
+}%
+
+\def\getcolorbyvalue#1{
+ \csname pgfpl@cm@\pgfkeysvalueof{/pgfplots/colormap name}@colspace\endcsname
+}
+
+\makeatother
+
+\def\setschemecolorsfrommap{
+ \extractcolormapcolor{scol0}{0}
+ \extractcolormapcolor{scol1}{333}
+ \extractcolormapcolor{scol2}{666}
+ \extractcolormapcolor{scol3}{1000}
+}
+
+\newcommand{\setschemecolorsmanually}[4]{
+ \colorlet{scol0}{#1}
+ \colorlet{scol1}{#2}
+ \colorlet{scol2}{#3}
+ \colorlet{scol3}{#4}
+}
+
+% Define color schemes
+
+\pgfplotsset{
+ /pgfplots/colorscheme/cel/.style={
+ colormap name={cel},
+ cycle list name={cel},
+ /utils/exec={\setschemecolorsfrommap},
+ },
+ /pgfplots/colorscheme/rocket/.style={
+ colormap name={rocket},
+ cycle list name={rocket},
+ /utils/exec={\setschemecolorsfrommap},
+ },
+ /pgfplots/colorscheme/viridis/.style={
+ colormap name={viridis},
+ cycle list name={viridis},
+ /utils/exec={\setschemecolorsfrommap},
+ },
+ /pgfplots/colorscheme/mako/.style={
+ colormap name={mako},
+ cycle list name={mako},
+ /utils/exec={\setschemecolorsfrommap},
+ },
+ /pgfplots/colorscheme/cividis/.style={
+ colormap name={cividis},
+ cycle list name={cividis},
+ /utils/exec={\setschemecolorsfrommap},
+ },
+ /pgfplots/colorscheme/matplotlib/.style={
+ colormap name={matplotlib},
+ cycle list name={matplotlib},
+ /utils/exec={\setschemecolorsmanually{Mpl1}{Mpl2}{Mpl3}{Mpl4}},
+ },
+}
+
diff --git a/res/report.tex b/banking_breakdown/document_builder/report.tex
similarity index 99%
rename from res/report.tex
rename to banking_breakdown/document_builder/report.tex
index 88dd9b7..3abaa24 100644
--- a/res/report.tex
+++ b/banking_breakdown/document_builder/report.tex
@@ -22,7 +22,7 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-\input{../lib/latex-common/common.tex}
+\input{common.tex}
\pgfplotsset{colorscheme/rocket}
diff --git a/banking_breakdown/test.json b/banking_breakdown/test.json
deleted file mode 100644
index bbe1003..0000000
--- a/banking_breakdown/test.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "asdf": [
- "Kinemic"
- ]
-}
\ No newline at end of file
diff --git a/banking_breakdown/ui/custom_ui_items.py b/banking_breakdown/ui/custom_ui_items.py
index 08dfc11..f2e66e5 100644
--- a/banking_breakdown/ui/custom_ui_items.py
+++ b/banking_breakdown/ui/custom_ui_items.py
@@ -4,8 +4,11 @@ from PyQt6.QtGui import QPixmap, QAction
from PyQt6.QtWidgets import QHBoxLayout, QLabel, QMenu, QInputDialog, \
QMessageBox
+import banking_breakdown.ui
from banking_breakdown.ui.pandas_model import PandasModel
+from importlib import resources
+
class WarningItem(QHBoxLayout):
"""Item appearing at top of Window with warning icon."""
@@ -14,7 +17,8 @@ class WarningItem(QHBoxLayout):
super(WarningItem, self).__init__()
self._warningIcon = QLabel()
- pixmap = QPixmap("res/warning.png")
+ pixmap_path = f"{resources.files(banking_breakdown.ui)}/warning.png"
+ pixmap = QPixmap(pixmap_path)
self._warningIcon.setPixmap(pixmap)
self._label = QLabel(text)
diff --git a/banking_breakdown/ui/main_window.py b/banking_breakdown/ui/main_window.py
index 85a293d..3de6812 100644
--- a/banking_breakdown/ui/main_window.py
+++ b/banking_breakdown/ui/main_window.py
@@ -6,15 +6,19 @@ from PyQt6.QtGui import QAction
from PyQt6.QtWidgets import QMainWindow, QPushButton, QVBoxLayout, \
QTableView, QInputDialog, QMessageBox, QFileDialog, QListWidget
+import banking_breakdown.ui
from banking_breakdown.ui.pandas_model import PandasModel
from banking_breakdown.ui.custom_ui_items import WarningItem, HeaderContextMenu
+from importlib import resources
+
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
- uic.loadUi("res/main_window.ui", self)
+ ui_path = f"{resources.files(banking_breakdown.ui)}/main_window.ui"
+ uic.loadUi(ui_path, self)
self.setContentsMargins(9, 9, 9, 9)
self._warnings = []
@@ -25,6 +29,8 @@ class MainWindow(QMainWindow):
= self.findChild(QVBoxLayout, "warningLayout")
self._create_button \
= self.findChild(QPushButton, "createCategoryButton")
+ self._rename_button \
+ = self.findChild(QPushButton, "renameCategoryButton")
self._delete_button \
= self.findChild(QPushButton, "deleteCategoryButton")
self._apply_button \
@@ -38,6 +44,11 @@ class MainWindow(QMainWindow):
self._action_save \
= self.findChild(QAction, "actionSave")
+ # Set scrolling behavior
+
+ self._table_view.horizontalScrollBar().setSingleStep(10)
+ self._table_view.verticalScrollBar().setSingleStep(10)
+
# Set up QTableView model
self._pandas_model = PandasModel(self)
@@ -50,6 +61,7 @@ class MainWindow(QMainWindow):
# Set event handlers
self._create_button.clicked.connect(self._handle_create_click)
+ self._rename_button.clicked.connect(self._handle_rename_click)
self._delete_button.clicked.connect(self._handle_delete_click)
self._clear_button.clicked.connect(self._handle_clear_click)
self._apply_button.clicked.connect(self._handle_apply_click)
@@ -167,6 +179,25 @@ class MainWindow(QMainWindow):
callback=self._dataframe_update_callback)
context.exec(self.sender().mapToGlobal(pos))
+ def _handle_rename_click(self):
+ selected_item = self._list_widget.currentItem()
+
+ new_name, flag = QInputDialog.getText(self, "Rename category",
+ "New name:",
+ text=selected_item.text())
+ if not flag:
+ return
+
+ current_items = [self._list_widget.item(x).text() for x
+ in range(self._list_widget.count())]
+
+ if new_name not in current_items:
+ self._pandas_model.rename_category(selected_item.text(), new_name)
+ selected_item.setText(new_name)
+ else:
+ QMessageBox.warning(self, "No action performed",
+ f"Category '{new_name}' already exists.")
+
def _handle_create_click(self):
new_name, flag = QInputDialog.getText(self, "Create category",
"New category:",
@@ -184,7 +215,7 @@ class MainWindow(QMainWindow):
f"Category '{new_name}' already exists.")
def _handle_delete_click(self):
- selected_item = self._list_widget.selectedItems()[0]
+ selected_item = self._list_widget.currentItem()
button = QMessageBox.question(self, "Delete category",
f"Are you sure you want to delete"
@@ -236,9 +267,16 @@ class MainWindow(QMainWindow):
else:
self._apply_button.setEnabled(False)
+ def _check_enable_rename_button(self):
+ if (len(self._list_widget.selectedItems()) > 0):
+ self._rename_button.setEnabled(True)
+ else:
+ self._rename_button.setEnabled(False)
+
def _handle_list_selection_changed(self):
self._check_enable_delete_button()
self._check_enable_apply_button()
+ self._check_enable_rename_button()
def _handle_table_selection_changed(self):
self._check_enable_clear_button()
diff --git a/res/main_window.ui b/banking_breakdown/ui/main_window.ui
similarity index 78%
rename from res/main_window.ui
rename to banking_breakdown/ui/main_window.ui
index 978c26f..1aa41f5 100644
--- a/res/main_window.ui
+++ b/banking_breakdown/ui/main_window.ui
@@ -60,6 +60,36 @@
+ -
+
+
+ false
+
+
+ Rename selected category
+
+
+ Rename
+
+
+
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
-
@@ -80,6 +110,9 @@
QAbstractScrollArea::AdjustToContents
+
+ true
+
-
@@ -123,9 +156,18 @@
-
+
+ false
+
QAbstractItemView::SelectRows
+
+ QAbstractItemView::ScrollPerPixel
+
+
+ QAbstractItemView::ScrollPerPixel
+
true
diff --git a/banking_breakdown/ui/pandas_model.py b/banking_breakdown/ui/pandas_model.py
index 61d6825..47bef99 100644
--- a/banking_breakdown/ui/pandas_model.py
+++ b/banking_breakdown/ui/pandas_model.py
@@ -83,6 +83,14 @@ class PandasModel(QtCore.QAbstractTableModel):
row_indices = self._data.loc[self._data['category'] == category].index
self.assign_category(' ', row_indices)
+ def rename_category(self, old_name, new_name):
+ if 'category' not in self._data.columns:
+ self.create_column('category')
+
+ row_indices = self._data.loc[self._data['category'] == old_name].index
+ self.assign_category(new_name, row_indices)
+
+
def get_categories(self) -> typing.List[str]:
if 'category' not in self._data.columns:
self.create_column('category')
diff --git a/res/warning.png b/banking_breakdown/ui/warning.png
similarity index 100%
rename from res/warning.png
rename to banking_breakdown/ui/warning.png
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..dadc460
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,4 @@
+numpy==1.26.3
+pandas==1.5.3
+PyQt6==6.6.1
+setuptools==69.0.3
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..709ea27
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,20 @@
+import os
+from setuptools import setup
+
+
+with open('requirements.txt') as f:
+ required = f.read().splitlines()
+
+
+setup(
+ name='banking_breakdown',
+ version='1.0',
+ description='Visualize bank statements',
+ author='Andreas Tsouchlos',
+ packages=['banking_breakdown', 'banking_breakdown.ui', 'banking_breakdown.document_builder'],
+ install_requires=required,
+ package_data={'banking_breakdown.ui': ['main_window.ui', 'warning.png'],
+ 'banking_breakdown.document_builder': ['.latexmkrc', 'Dockerfile.alpine', 'report.tex', 'common.tex']},
+ include_package_data=True,
+)
+