6 Commits

Author SHA1 Message Date
7cf3f6cc1f multi line toggle active 2025-04-18 12:27:08 +02:00
62e8bb728b adds inc and dec sort for table header 2025-04-18 12:13:51 +02:00
5f568d3850 vertical scrollbar for table 2025-04-18 12:03:20 +02:00
fb2eb44309 removes json from compilation 2025-04-18 09:58:20 +02:00
71f35d6aab implemented settings 2025-04-18 09:57:17 +02:00
6319e0af1d remove content from subfiles (unneeded) 2025-04-18 09:56:46 +02:00
4 changed files with 211 additions and 141 deletions

View File

@@ -1,12 +1,92 @@
import csv import csv
import os import os
import sys
import tkinter as tk import tkinter as tk
from configparser import NoOptionError from tkinter import font, filedialog
from configparser import NoSectionError, NoOptionError
from configparser import ConfigParser from configparser import ConfigParser
from tkinter import messagebox from tkinter import messagebox
from tkinter import ttk from tkinter import ttk
import json import json
from PyInstaller.utils.hooks import collect_data_files
class SettingsWindow(tk.Toplevel):
def __init__(self, root: tk.Tk):
super().__init__()
self.root = root
self.protocol("WM_DELETE_WINDOW", self.close_window)
self.geometry(f"500x330+{self.root.winfo_x() + 20}+{self.root.winfo_y() + 20}")
self.config = Config()
self.json_file = tk.StringVar()
self.csv_file = tk.StringVar()
title = font.Font(family='Ubuntu Mono', size=20, weight=font.BOLD)
tk.Label(self, text="Einstellungen", font=title).pack(pady=20)
path_frame = tk.Frame(self)
path_frame.pack(pady=(10, 40))
tk.Label(path_frame, text="Datenpfad JSON Datei").grid(row=0, column=0)
tk.Entry(path_frame, textvariable=self.json_file, width=50).grid(row=1, column=0)
tk.Button(path_frame, text="Pfad", command=self.set_json_path).grid(row=1, column=1)
tk.Label(path_frame, text="Datenpfad CSV Export Datei").grid(row=2, column=0)
tk.Entry(path_frame, textvariable=self.csv_file, width=50).grid(row=3, column=0)
tk.Button(path_frame, text="Pfad", command=self.set_csv_path).grid(row=3, column=1)
bottom_frame = tk.Frame(self)
bottom_frame.pack()
tk.Button(bottom_frame, text="Ok", command=self.ok).pack(side=tk.LEFT)
tk.Button(bottom_frame, text="Abbrechen", command=self.cancel).pack(side=tk.LEFT)
self.load_config()
def set_json_path(self):
self.json_file.set(filedialog.askdirectory(initialdir="~/", title="Datenpfad JSON Datei"))
def set_csv_path(self):
self.csv_file.set(filedialog.askdirectory(initialdir="~/", title="Datenpfad CSV Datei"))
def load_config(self):
try:
self.json_file.set(self.config.get("json", "path"))
except NoSectionError:
self.config.add_section("json")
self.config.set("json", "path", "")
except NoOptionError:
self.config.set("json", "path", "")
try:
self.csv_file.set(self.config.get("csv", "path"))
except NoSectionError:
self.config.add_section("csv")
self.config.set("csv", "path", "")
except NoOptionError:
self.config.set("csv", "path", "")
def ok(self):
self.config.set("json", "path", self.json_file.get())
self.config.set("csv", "path", self.csv_file.get())
self.close_window()
def cancel(self):
self.close_window()
def close_window(self):
if self.json_file.get() == "" or self.csv_file.get() == "":
messagebox.showwarning(title="Fehlerhafte Konfiguration", message="Pfad für JSON oder CSV Datei fehlt")
return
self.destroy_window()
def destroy_window(self):
self.root.update()
self.root.deiconify()
self.destroy()
class Config: class Config:
parser: ConfigParser parser: ConfigParser
@@ -51,31 +131,48 @@ class Config:
class Application: class Application:
def __init__(self): def __init__(self):
self.config = Config()
self.address_list = []
self.current_record: int | None = None
# json vars
self.json_file_name = "files/address_data.json"
self.root_path = os.path.dirname(os.path.abspath(__file__))
self.json_file = os.path.join(self.root_path, self.json_file_name)
# tkinter settings # tkinter settings
x_offset = 700 x_offset = 700
y_offset = 400 y_offset = 200
width = 1050
height = 1000
title = "Brovski Adress-Etiketten Verwaltung" title = "Brovski Adress-Etiketten Verwaltung"
self.root = tk.Tk(className="BrovskiAdressEtiketten") self.root = tk.Tk(className="BrovskiAdressEtiketten")
self.root.title(title) self.root.title(title)
self.root.geometry(f"+{x_offset}+{y_offset}") self.root.protocol("WM_DELETE_WINDOW", self.on_close)
self.root.geometry(f"{width}x{height}+{x_offset}+{y_offset}")
self.config = Config()
self.config_good = False
# variables
self.address_list = []
self.current_record: int | None = None
self.sort_order = False
# init paths to json and csv file
self.json_file_name = "brovski-adress-etiketten-verwaltung.json"
self.csv_file_name = "brovski-adress-etiketten.csv"
self.json_path = ""
self.csv_path = ""
self.load_config()
self.json_file = os.path.join(self.json_path, self.json_file_name)
self.csv_file = os.path.join(self.csv_path, self.csv_file_name)
# leave application if settings are bad
if not self.config_good:
self.show_error("Fehler Konfiguration",
"Die Konfiguration ist fehlerhaft, bitte prüfe deine config.ini")
sys.exit()
top_frame = tk.Frame(self.root) top_frame = tk.Frame(self.root)
top_frame.pack(side=tk.TOP, fill=tk.X) top_frame.pack(side=tk.TOP, fill=tk.X)
button_width = 8 button_width = 8
tk.Button(top_frame, text="Export CSV", command=self.export_csv, width=button_width).grid(row=0, column=2) tk.Button(top_frame, text="Insert", command=self.insert_record, width=button_width).pack(side=tk.LEFT)
tk.Button(top_frame, text="Insert", command=self.insert_record, width=button_width).grid(row=0, column=0) tk.Button(top_frame, text="Delete", command=self.delete_record, width=button_width).pack(side=tk.LEFT)
tk.Button(top_frame, text="Delete", command=self.delete_record, width=button_width).grid(row=0, column=1) tk.Button(top_frame, text="Export CSV", command=self.export_csv, width=button_width).pack(side=tk.LEFT)
tk.Button(top_frame, text="Quit", command=self.on_close, width=button_width).pack(side=tk.RIGHT)
tk.Button(top_frame, text="Settings", command=self.show_settings, width=button_width).pack(side=tk.RIGHT)
self.aktiv = tk.StringVar() self.aktiv = tk.StringVar()
self.firma = tk.StringVar() self.firma = tk.StringVar()
@@ -100,25 +197,56 @@ class Application:
edit_strasse.grid(row=1, column=3) edit_strasse.grid(row=1, column=3)
edit_plz_ort = tk.Entry(edit_frame, textvariable=self.plz_ort) edit_plz_ort = tk.Entry(edit_frame, textvariable=self.plz_ort)
edit_plz_ort.grid(row=1, column=4) edit_plz_ort.grid(row=1, column=4)
tk.Button(edit_frame, text="Update", command=self.update_record).grid(row=1, column=5) tk.Button(edit_frame, text="Update", command=self.update_record, width=button_width).grid(row=1, column=5)
tk.Button(edit_frame, text="Toggle Aktiv", command=self.toggle_active, width=button_width).grid(row=1, column=6)
data_frame = tk.Frame(self.root, bg="teal") data_frame = tk.Frame(self.root, bg="teal")
data_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True) data_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
self.table = ttk.Treeview(data_frame, columns=("0", "1", "2", "3", "4"), show="headings") scrollbar = ttk.Scrollbar(data_frame, orient=tk.VERTICAL)
self.table = ttk.Treeview(data_frame, yscrollcommand=scrollbar.set, columns=("0", "1", "2", "3", "4"),
show="headings")
scrollbar.config(command=self.table.yview)
self.table.heading('0', text="Aktiv") self.table.heading('0', text="Aktiv")
self.table.column('0', anchor=tk.CENTER, width=0)
self.table.heading('1', text="Firma") self.table.heading('1', text="Firma")
self.table.heading('2', text="Name") self.table.heading('2', text="Name")
self.table.heading('3', text="Strasse") self.table.heading('3', text="Strasse")
self.table.heading('4', text="Plz/Ort") self.table.heading('4', text="Plz/Ort")
self.table.pack() self.table.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.LEFT, fill=tk.Y)
self.table.bind("<ButtonRelease-1>", self.mouse_click) self.table.bind("<ButtonRelease-1>", self.mouse_click)
self._load_file() self._load_json_file()
self.root.mainloop() self.root.mainloop()
def load_config(self):
try:
self.json_path = self.config.get("json", "path")
self.csv_path = self.config.get("csv", "path")
self.config_good = True
except NoSectionError:
self.show_config_error()
self.show_settings()
except NoOptionError:
self.show_config_error()
self.show_settings()
def show_config_error(self):
if self.show_error("Fehlerhafte Konfiguration",
"Konnte benötigte Parameter in config.ini nicht finden"):
print("Fehlerhafte Konfiguration")
def on_close(self):
self.root.destroy()
def show_settings(self):
self.root.withdraw()
settings = SettingsWindow(self.root)
settings.wait_window()
def insert_record(self): def insert_record(self):
if self.current_record is not None: if self.current_record is not None:
self.clear_entry_fields() self.clear_entry_fields()
@@ -131,7 +259,7 @@ class Application:
"Plz/Ort", "Plz/Ort",
] ]
self.table.insert('', 'end', values=values) self.table.insert('', 'end', values=values)
self._save_file() self._save_json_file()
def delete_record(self): def delete_record(self):
if self.current_record is None: if self.current_record is None:
@@ -143,45 +271,7 @@ class Application:
self.table.delete(self.current_record) self.table.delete(self.current_record)
self.clear_entry_fields() self.clear_entry_fields()
self._save_file() self._save_json_file()
def clear_entry_fields(self):
entry_var_list = [self.aktiv, self.firma, self.name, self.strasse, self.plz_ort]
for entry in entry_var_list:
entry.set("")
self.current_record = None
def mouse_click(self, event):
region = self.table.identify("region", event.x, event.y)
if region == "heading":
column = self.table.identify_column(event.x)
match column:
case "#1":
self.address_list.sort(key=lambda x: x[int(column[-1]) - 1])
self.populate_table()
case "#2":
self.address_list.sort(key=lambda x: x[int(column[-1]) - 1])
self.populate_table()
case "#3":
self.address_list.sort(key=lambda x: x[int(column[-1]) - 1])
self.populate_table()
case "#4":
self.address_list.sort(key=lambda x: x[int(column[-1]) - 1])
self.populate_table()
case "#5":
self.address_list.sort(key=lambda x: x[int(column[-1]) - 1])
self.populate_table()
case _:
print(column)
if region == "cell":
self.current_record = self.table.focus()
values = self.table.item(self.current_record, "values")
entry_var_list = [self.aktiv, self.firma, self.name, self.strasse, self.plz_ort]
for i in range(len(values)):
entry_var_list[i].set(values[i])
def update_record(self): def update_record(self):
if self.current_record is None: if self.current_record is None:
@@ -197,24 +287,76 @@ class Application:
self.table.set(self.current_record, key, value) self.table.set(self.current_record, key, value)
self.clear_entry_fields() self.clear_entry_fields()
self._save_file() self._save_json_file()
def _load_file(self): def toggle_active(self):
items = self.table.selection()
if len(items) == 0:
return
for id in items:
values = self.table.item(id, "values")
active = values[0]
new_active = "x" if active == "" else ""
self.table.set(id, "0", new_active)
self.clear_entry_fields()
self._save_json_file()
def clear_entry_fields(self):
entry_var_list = [self.aktiv, self.firma, self.name, self.strasse, self.plz_ort]
for entry in entry_var_list:
entry.set("")
self.current_record = None
def mouse_click(self, event):
region = self.table.identify("region", event.x, event.y)
if region == "heading":
column = self.table.identify_column(event.x)
match column:
case "#1":
self.address_list.sort(key=lambda x: x[int(column[-1]) - 1], reverse=self.sort_order)
self.populate_table()
case "#2":
self.address_list.sort(key=lambda x: x[int(column[-1]) - 1], reverse=self.sort_order)
self.populate_table()
case "#3":
self.address_list.sort(key=lambda x: x[int(column[-1]) - 1], reverse=self.sort_order)
self.populate_table()
case "#4":
self.address_list.sort(key=lambda x: x[int(column[-1]) - 1], reverse=self.sort_order)
self.populate_table()
case "#5":
self.address_list.sort(key=lambda x: x[int(column[-1]) - 1], reverse=self.sort_order)
self.populate_table()
case _:
print(column)
self.sort_order = not self.sort_order
if region == "cell":
self.current_record = self.table.focus()
values = self.table.item(self.current_record, "values")
entry_var_list = [self.aktiv, self.firma, self.name, self.strasse, self.plz_ort]
for i in range(len(values)):
entry_var_list[i].set(values[i])
def _load_json_file(self):
try: try:
with open(self.json_file, "r", encoding="utf-8") as f: with open(self.json_file, "r", encoding="utf-8") as f:
self.address_list = json.load(f) self.address_list = json.load(f)
self.address_list.sort(key=lambda x: (x[0], x[1])) self.address_list.sort(key=lambda x: (x[0], x[1]))
except FileNotFoundError: except FileNotFoundError:
self.show_error( self.show_error(
message_title="File not found", message_title="Datei nicht gefunden",
message=f"{self.json_file_name} not found, creating empty file at {self.root_path}" message=f"{self.json_file_name} nicht gefunden, erstelle leere Datei unter {self.json_path}"
) )
self.address_list = [["firma", "name", "adresse", "plz/ort"]] self.address_list = [["", "firma", "name", "adresse", "plz/ort"]]
with open(self.json_file, "w", encoding="utf-8") as f: with open(self.json_file, "w", encoding="utf-8") as f:
json.dump(self.address_list, f) json.dump(self.address_list, f)
self.populate_table() self.populate_table()
def _save_file(self): def _save_json_file(self):
self.export_table_to_address_list() self.export_table_to_address_list()
try: try:
with open(self.json_file, "w", encoding="utf-8") as f: with open(self.json_file, "w", encoding="utf-8") as f:
@@ -226,31 +368,7 @@ class Application:
def export_csv(self): def export_csv(self):
try: try:
file_name = self.config.get("csv", "file_name") with open(self.csv_file, "w", encoding="utf-8") as f:
except NoOptionError:
self.show_error("Error: Option missing", "Option file_name is missing [csv]")
return
try:
path = self.config.get("csv", "path")
except NoOptionError:
self.show_error("Error: Option missing", "Option path is missing [csv]")
return
if file_name == "":
self.show_error("Error: Bad config file", "var file_name for section [csv] not set")
return
if path == "":
self.show_error(
"Alert: CSV path not set",
f"var path for section [csv] not set, file will be saved in {self.root_path}"
)
path = self.root_path
csv_file = os.path.join(path, file_name)
try:
with open(csv_file, "w", encoding="utf-8") as f:
writer = csv.writer(f, delimiter=",") writer = csv.writer(f, delimiter=",")
for address in self.address_list: for address in self.address_list:
if address[0] != "x": if address[0] != "x":
@@ -260,7 +378,7 @@ class Application:
del address[0] del address[0]
writer.writerow(address) writer.writerow(address)
except FileNotFoundError: except FileNotFoundError:
self.show_error("Unexpected error", f"Could not write file {csv_file}") self.show_error("Unexpected error", f"Could not write file {self.csv_file}")
def populate_table(self): def populate_table(self):
self.delete_all_table_items() self.delete_all_table_items()

View File

@@ -1,2 +1,2 @@
#!/usr/bin/env bash #!/usr/bin/env bash
pyinstaller --clean --add-data=files/config.ini:files --add-data=files/address_data.json:files brovski-adress-etiketten-verwaltung.py pyinstaller --clean --add-data=files/config.ini:files brovski-adress-etiketten-verwaltung.py

View File

@@ -1,45 +0,0 @@
[
[
"x",
"Matthias Freilinger",
"Gibelstrasse 8",
"6231 Schlierbach"
],
[
"x",
"Andreas Fischer",
"Dorfrain 3A",
"5702 Niederlenz"
],
[
"x",
"Chrege Braun",
"Emausstrasse 21",
"5621 Zufikon"
],
[
"x",
"Sarah Märki",
"Hubgasse 6",
"8570 Weinfelden"
],
[
"x",
"Richard Hanselmann",
"Schemel 90",
"5077 Elfingen"
],
[
"x",
"Simon Lüthi",
"Delphinweg 1",
"5616 Meisterschwanden"
],
[
"x",
"Jacqueline Felder",
"Rappenmööslistr. 20",
"8840 Einsiedeln"
]
]

View File

@@ -1,3 +0,0 @@
[csv]
file_name=address.csv
path=