import csv import os import sys import tkinter as tk from configparser import NoSectionError, NoOptionError from tkinter import messagebox from tkinter import ttk from config import Config from connector import JSONConnector from model import Model from windows import SettingsWindow, EditRecord, show_error class Application: def __init__(self): # tkinter settings x_offset = 700 y_offset = 200 width = 1050 height = 700 VERSION = '0.1.0b' title = f"Brovski Adress-Etiketten Verwaltung {VERSION}" self.root = tk.Tk(className="BrovskiAdressEtiketten") self.root.title(title) 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 self.last_sort_field = "#3" self.filter_active = tk.BooleanVar(value=False) # model connector self.model = Model(JSONConnector()) # 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.csv_path = "" self.load_config() self.csv_file = os.path.join(self.csv_path, self.csv_file_name) # status bar content self.statusbar = tk.StringVar() self.length_address_list = None self.length_address_list_active = None # leave application if settings are bad if not self.config_good: show_error(message_title="Fehler Konfiguration", message="Die Konfiguration ist fehlerhaft, bitte prüfe deine config.ini", parent=self.root ) sys.exit() # frames top_frame = tk.Frame(self.root) top_frame.pack(side=tk.TOP, fill=tk.X) data_frame = tk.Frame(self.root, bg="teal") data_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True) bottom_frame = tk.Frame(self.root) bottom_frame.pack(side=tk.BOTTOM, fill=tk.X) # top buttons button_width = 8 tk.Button(top_frame, text="Insert", command=self.insert_record, width=button_width).pack(side=tk.LEFT) tk.Button(top_frame, text="Delete", command=self.delete_record, width=button_width).pack(side=tk.LEFT) tk.Button(top_frame, text="Export CSV", command=self.export_csv, width=button_width).pack(side=tk.LEFT) tk.Button(top_frame, text="Toggle Aktiv", command=self.toggle_active, width=button_width).pack(side=tk.LEFT) tk.Checkbutton(top_frame, text="Filter aktiv", variable=self.filter_active, command=self.populate_table).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) # table content 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.column('0', anchor=tk.CENTER, width=0) self.table.heading('1', text="Firma") self.table.heading('2', text="Name") self.table.heading('3', text="Strasse") self.table.heading('4', text="Plz/Ort") self.table.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scrollbar.pack(side=tk.LEFT, fill=tk.Y) self.table.bind("", self.mouse_click) self.table.bind("", self.enter_button) self.table.bind("", self.mouse_click_double) self.root.bind("", self.focus_table) self.root.bind("", self.focus_table) # bottom status bar tk.Label(bottom_frame, textvariable=self.statusbar).pack(side=tk.LEFT) self.first_sort_after_start() self.root.mainloop() def load_config(self): try: self.csv_path = self.config.get("csv", "path") if self.csv_path == "": raise ValueError("Empty JSON or CSV path") self.config_good = True except NoSectionError: self.show_config_error() self.show_settings() except NoOptionError: self.show_config_error() self.show_settings() except ValueError: self.show_config_error() self.show_settings() def show_config_error(self): show_error(message_title="Fehlerhafte Konfiguration", message="Konnte benötigte Parameter in config.ini nicht finden", parent=self.root) def on_close(self): self.root.destroy() def show_settings(self): settings = self.open_window_settings() settings.wait_window() def insert_record(self): values = { "aktiv": "x", "firma": "Firma", "name": "Name", "strasse": "Strasse", "plzort": "Plz/Ort" } self.model.create_new(values) self.populate_table() self.deselect_tree() def delete_record(self): if self.current_record is None: return if len(self.table.selection()) > 1: show_error(message_title="Mehrere Adressen ausgewählt", message="Es können nur einzelne Adressen gelöscht werden", parent=self.root) return if messagebox.askyesno( "Eintrag löschen?", "Willst du diesen Eintrag wirklich löschen?\nDies kann nicht rückgängig gemacht werden"): self.model.delete_by_id(self.current_record) self.deselect_tree() self.populate_table() def update_record(self, record: dict): if self.current_record is None: return self.model.update_record(record) self.populate_table() def toggle_active(self): selection = self.table.selection() if len(selection) == 0: return item_list = [int(x) for x in selection] for address in self.address_list: record_id = address.get("record_id") if record_id in item_list: address["aktiv"] = "x" if address["aktiv"] == "" else "" self.model.update_record(address) self.table.set(record_id, "0", address["aktiv"]) self.update_status_bar() def deselect_tree(self): while len(self.table.selection()) > 0: self.table.selection_remove(self.table.selection()[0]) self.current_record = None def mouse_click(self, event): id_string = self.table.focus() if id_string is not None and id_string != "": self.current_record = int(self.table.focus()) else: self.current_record = None region = self.table.identify("region", event.x, event.y) match region: case "heading": self.click_on_header(event) def mouse_click_double(self, event): region = self.table.identify("region", event.x, event.y) match region: case "cell": self.edit_selected_record() def enter_button(self, event): self.edit_selected_record() def click_on_header(self, event): column = self.table.identify_column(event.x) if self.last_sort_field == column: self.sort_order = False if self.sort_order else True else: self.sort_order = False self.last_sort_field = column match column: case "#1": field = "aktiv" case "#2": field = "firma" case "#3": field = "name" case "#4": field = "strasse" case "#5": field = "plzort" case _: field = "name" self.address_list = self.model.get_all_sorted_by(field, self.sort_order) self.populate_table(reload=False) def edit_selected_record(self): if self.table.focus() is None or self.table.focus() == "": return self.current_record = int(self.table.focus()) self.open_window_edit_records(self.current_record) def export_csv(self): try: with open(self.csv_file, "w", encoding="utf-8") as f: writer = csv.writer(f, delimiter=",") for address in self.address_list: if address["aktiv"] != "x": continue line = [] if address["firma"] != "": line.append(address["firma"]) line.append(address["name"]) line.append(address["strasse"]) line.append(address["plzort"]) writer.writerow(line) except FileNotFoundError: show_error(message_title="Unexpected error", message=f"Could not write file {self.csv_file}", parent=self.root ) def populate_table(self, reload=True): if reload: self.address_list = self.model.get_all() if len(self.address_list) == 0: return self.delete_all_table_items() for address in self.address_list: # skip inactive records if filter is true if self.filter_active.get() and address["aktiv"] != "x": continue self.table.insert('', 'end', iid=address["record_id"], values=(address["aktiv"], address["firma"], address["name"], address["strasse"], address["plzort"]) ) self.update_status_bar() def export_table_to_address_list(self): self.address_list.clear() for child in self.table.get_children(): self.address_list.append([]) for value in self.table.item(child)['values']: self.address_list[-1].append(value) self.update_status_bar() def delete_all_table_items(self): for item in self.table.get_children(): self.table.delete(item) def open_window_settings(self): window = SettingsWindow(self, self.root) window.wm_transient(self.root) window.wait_visibility() window.grab_set() return window def open_window_edit_records(self, record_id: int): window = EditRecord(self, self.root, record_id) window.wm_transient(self.root) window.wait_visibility() window.grab_set() def update_status_bar(self): self._count_address_records() self.statusbar.set(f"Adressen: {self.length_address_list} | Aktive Adressen: {self.length_address_list_active}") def _count_address_records(self): self.length_address_list = len(self.address_list) count = 0 for address in self.address_list: if address["aktiv"] == "x": count += 1 self.length_address_list_active = count def first_sort_after_start(self): self.address_list.sort(key=lambda x: (x["firma"], x["name"])) self.populate_table() def focus_table(self, event): first = self.table.get_children()[0] last = self.table.get_children()[-1] goto = last if event.keysym == "Up" else first if self.table.focus() == "": self.table.selection_set(goto) self.table.focus(goto) self.table.see(goto) self.table.focus_force() if __name__ == '__main__': Application()