From dc86d8ce0176349db8de2ae94e4498914498a3fc Mon Sep 17 00:00:00 2001 From: sroth Date: Sun, 20 Apr 2025 23:34:41 +0200 Subject: [PATCH] refactored entry mask to own window, split windows and config from main app file --- src/brovski-adress-etiketten-verwaltung.py | 229 +++------------------ src/config.py | 43 ++++ src/windows.py | 148 +++++++++++++ 3 files changed, 223 insertions(+), 197 deletions(-) create mode 100644 src/config.py create mode 100644 src/windows.py diff --git a/src/brovski-adress-etiketten-verwaltung.py b/src/brovski-adress-etiketten-verwaltung.py index 0548bf2..5974532 100644 --- a/src/brovski-adress-etiketten-verwaltung.py +++ b/src/brovski-adress-etiketten-verwaltung.py @@ -2,174 +2,13 @@ import csv import os import sys import tkinter as tk -from tkinter import font, filedialog from configparser import NoSectionError, NoOptionError -from configparser import ConfigParser from tkinter import messagebox from tkinter import ttk import json - -def show_error(message_title: str, message: str, parent: tk.Tk | tk.Toplevel): - messagebox.showwarning( - title=message_title, - message=message, - parent=parent - ) - - -class Window(tk.Toplevel): - def __init__(self, root: tk.Tk): - super().__init__(root) - self.root = root - self.protocol("WM_DELETE_WINDOW", self.close_window) - - def close_window(self): - self.destroy_window() - - def destroy_window(self): - self.root.update() - self.destroy() - - -class InsertRecord(Window): - def __init__(self, root: tk.Tk): - super().__init__(root) - self.title("Adresse einfügen") - - edit_frame = tk.Frame(self.root) - edit_frame.pack(side=tk.TOP, fill=tk.X) - tk.Label(edit_frame, text="Aktiv").grid(row=0, column=0) - tk.Label(edit_frame, text="Firma").grid(row=0, column=1) - tk.Label(edit_frame, text="Name").grid(row=0, column=2) - tk.Label(edit_frame, text="Strasse").grid(row=0, column=3) - tk.Label(edit_frame, text="Plz/Ort").grid(row=0, column=4) - edit_aktiv = tk.Checkbutton(edit_frame, variable=self.aktiv, onvalue="x", offvalue="") - edit_aktiv.grid(row=1, column=0) - edit_firma = tk.Entry(edit_frame, textvariable=self.firma) - edit_firma.grid(row=1, column=1) - edit_name = tk.Entry(edit_frame, textvariable=self.name) - edit_name.grid(row=1, column=2) - edit_strasse = tk.Entry(edit_frame, textvariable=self.strasse) - edit_strasse.grid(row=1, column=3) - edit_plz_ort = tk.Entry(edit_frame, textvariable=self.plz_ort) - edit_plz_ort.grid(row=1, column=4) - tk.Button(edit_frame, text="Update", command=self.update_record, width=button_width).grid(row=1, column=5) - tk.Button(self, text="Abbrechen", command=self.close_window).pack() - - -class SettingsWindow(Window): - def __init__(self, root: tk.Tk): - super().__init__(root) - 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=lambda: self.set_json_path(self.json_file.get())).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=lambda: self.set_csv_path(self.csv_file.get())).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, initial_dir: str = ""): - initial_dir = "~/" if initial_dir == "" else initial_dir - new_path = filedialog.askdirectory(initialdir=initial_dir, title="Datenpfad JSON Datei") - new_path = initial_dir if len(new_path) == 0 else new_path - self.json_file.set(new_path) - - def set_csv_path(self, initial_dir: str = ""): - initial_dir = "~/" if initial_dir == "" else initial_dir - new_path = filedialog.askdirectory(initialdir=initial_dir, title="Datenpfad CSV Datei") - new_path = initial_dir if len(new_path) == 0 else new_path - self.csv_file.set(new_path) - - 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): - if self.json_file.get() == "" or self.csv_file.get() == "": - show_error(message_title="Fehlerhafte Konfiguration", - message="Pfad für JSON oder CSV Datei fehlt", - parent=self) - return - 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() - - -class Config: - parser: ConfigParser - - def __init__(self): - """ - Config parser reading config.ini - - Attributes: - self.parser: ConfigParser holding list of sections and options - self.__filename: Path and name to the config file - """ - self.parser = ConfigParser() - - self.config_file_name = "config.ini" - self.root_path = os.path.dirname(os.path.abspath(__file__)) - self.config_file = os.path.join(self.root_path, self.config_file_name) - - self._load() - - def _save(self): - with open(self.config_file, 'w') as outfile: - self.parser.write(outfile) - - def _load(self): - self.parser.read(self.config_file) - - def add_section(self, section): - self._load() - self.parser.add_section(section) - self._save() - - def set(self, section: str, option: str, value: str): - self._load() - self.parser.set(section, option, value) - self._save() - - def get(self, section: str, option: str): - self._load() - return self.parser.get(section, option) +from config import Config +from windows import SettingsWindow, EditRecord, Window, show_error class Application: @@ -220,13 +59,6 @@ class Application: 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.firma = tk.StringVar() - self.name = tk.StringVar() - self.strasse = tk.StringVar() - self.plz_ort = tk.StringVar() - - data_frame = tk.Frame(self.root, bg="teal") data_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True) @@ -244,6 +76,8 @@ class Application: scrollbar.pack(side=tk.LEFT, fill=tk.Y) self.table.bind("", self.mouse_click) + self.table.bind("", self.mouse_click_double) + self.table.bind("", self.mouse_click_double) self._load_json_file() @@ -275,12 +109,10 @@ class Application: self.root.destroy() def show_settings(self): - settings = self.open_window(SettingsWindow) + settings = self.open_window_settings() settings.wait_window() def insert_record(self): - if self.current_record is not None: - self.clear_entry_fields() values = [ "x", "Firma", @@ -288,7 +120,9 @@ class Application: "Strasse", "Plz/Ort", ] - self.table.insert('', 'end', values=values) + last_iid = self.table.get_children()[-1] + next_iid = int(last_iid) + 1 + self.table.insert('', 'end', iid=next_iid, values=values) self._save_json_file() def delete_record(self): @@ -300,24 +134,19 @@ class Application: "Willst du diesen Eintrag wirklich löschen?\nDies kann nicht rückgängig gemacht werden"): self.table.delete(self.current_record) - self.clear_entry_fields() self.deselect_tree() self._save_json_file() - def update_record(self): + def update_record(self, data: list): if self.current_record is None: return - values = { - 0: self.aktiv.get(), - 1: self.firma.get(), - 2: self.name.get(), - 3: self.strasse.get(), - 4: self.plz_ort.get(), - } + values = {} + for idx, value in enumerate(data): + values[str(idx)] = value.get() + for key, value in values.items(): self.table.set(self.current_record, key, value) - self.clear_entry_fields() self._save_json_file() def toggle_active(self): @@ -331,24 +160,23 @@ class Application: new_active = "x" if active == "" else "" self.table.set(record_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 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): + self.current_record = self.table.focus() 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.click_on_cell() @@ -373,14 +201,11 @@ class Application: case _: print(column) self.sort_order = not self.sort_order - self.clear_entry_fields() def click_on_cell(self): 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]) + self.open_window_edit_records(values) def _load_json_file(self): try: @@ -443,11 +268,21 @@ class Application: for item in self.table.get_children(): self.table.delete(item) - def open_window(self, child: type[Window]) -> Window: - window = child(self.root) + 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, data): + window = EditRecord(self, self.root, data) + window.wm_transient(self.root) + window.wait_visibility() + window.grab_set() + + + + if __name__ == '__main__': Application() diff --git a/src/config.py b/src/config.py new file mode 100644 index 0000000..008aa47 --- /dev/null +++ b/src/config.py @@ -0,0 +1,43 @@ +import os +from configparser import ConfigParser + + +class Config: + parser: ConfigParser + + def __init__(self): + """ + Config parser reading config.ini + + Attributes: + self.parser: ConfigParser holding list of sections and options + self.__filename: Path and name to the config file + """ + self.parser = ConfigParser() + + self.config_file_name = "config.ini" + self.root_path = os.path.dirname(os.path.abspath(__file__)) + self.config_file = os.path.join(self.root_path, self.config_file_name) + + self._load() + + def _save(self): + with open(self.config_file, 'w') as outfile: + self.parser.write(outfile) + + def _load(self): + self.parser.read(self.config_file) + + def add_section(self, section): + self._load() + self.parser.add_section(section) + self._save() + + def set(self, section: str, option: str, value: str): + self._load() + self.parser.set(section, option, value) + self._save() + + def get(self, section: str, option: str): + self._load() + return self.parser.get(section, option) diff --git a/src/windows.py b/src/windows.py new file mode 100644 index 0000000..b244f06 --- /dev/null +++ b/src/windows.py @@ -0,0 +1,148 @@ +import tkinter as tk +from configparser import NoSectionError, NoOptionError +from tkinter import font, filedialog, messagebox + +from config import Config + + +def show_error(message_title: str, message: str, parent: tk.Tk | tk.Toplevel): + messagebox.showwarning( + title=message_title, + message=message, + parent=parent + ) + + +class Window(tk.Toplevel): + def __init__(self, parent, root: tk.Tk): + super().__init__(root) + self.parent = parent + self.root = root + self.protocol("WM_DELETE_WINDOW", self.close_window) + self.bind("", self.close_window) + + def close_window(self, event = None): + self._destroy_window() + + def _destroy_window(self): + self.root.update() + self.destroy() + + +class EditRecord(Window): + def __init__(self, parent, root: tk.Tk, data: tuple): + super().__init__(parent, root) + self.bind("", self._update) + + self.data = data + button_width = 8 + self.title("Adresse bearbeiten") + self.aktiv = tk.StringVar() + self.firma = tk.StringVar() + self.name = tk.StringVar() + self.strasse = tk.StringVar() + self.plz_ort = tk.StringVar() + + self.field_list = [self.aktiv, self.firma, self.name, self.strasse, self.plz_ort] + for argument, field in zip(self.data, self.field_list): + field.set(argument) + + edit_frame = tk.Frame(self) + edit_frame.pack(side=tk.TOP, fill=tk.X, pady=20, padx=20) + tk.Label(edit_frame, text="Aktiv", anchor=tk.W).grid(row=0, column=0) + tk.Label(edit_frame, text="Firma").grid(row=0, column=1) + tk.Label(edit_frame, text="Name").grid(row=0, column=2) + tk.Label(edit_frame, text="Strasse").grid(row=0, column=3) + tk.Label(edit_frame, text="Plz/Ort").grid(row=0, column=4) + edit_aktiv = tk.Checkbutton(edit_frame, variable=self.aktiv, onvalue="x", offvalue="") + edit_aktiv.grid(row=1, column=0) + edit_firma = tk.Entry(edit_frame, textvariable=self.firma) + edit_firma.grid(row=1, column=1) + edit_name = tk.Entry(edit_frame, textvariable=self.name) + edit_name.grid(row=1, column=2) + edit_strasse = tk.Entry(edit_frame, textvariable=self.strasse) + edit_strasse.grid(row=1, column=3) + edit_plz_ort = tk.Entry(edit_frame, textvariable=self.plz_ort) + edit_plz_ort.grid(row=1, column=4) + + button_frame = tk.Frame(self) + button_frame.pack(side=tk.TOP, pady=10) + tk.Button(button_frame, text="Save", command=self._update, width=button_width).pack(side=tk.LEFT) + tk.Button(button_frame, text="Abbrechen", command=self.close_window, width=button_width).pack(side=tk.LEFT) + + def _update(self, event = None): + self.parent.update_record(self.field_list) + self.close_window() + + +class SettingsWindow(Window): + def __init__(self, parent, root: tk.Tk): + super().__init__(parent, root) + 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=lambda: self.set_json_path(self.json_file.get())).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=lambda: self.set_csv_path(self.csv_file.get())).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, initial_dir: str = ""): + initial_dir = "~/" if initial_dir == "" else initial_dir + new_path = filedialog.askdirectory(initialdir=initial_dir, title="Datenpfad JSON Datei") + new_path = initial_dir if len(new_path) == 0 else new_path + self.json_file.set(new_path) + + def set_csv_path(self, initial_dir: str = ""): + initial_dir = "~/" if initial_dir == "" else initial_dir + new_path = filedialog.askdirectory(initialdir=initial_dir, title="Datenpfad CSV Datei") + new_path = initial_dir if len(new_path) == 0 else new_path + self.csv_file.set(new_path) + + 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): + if self.json_file.get() == "" or self.csv_file.get() == "": + show_error(message_title="Fehlerhafte Konfiguration", + message="Pfad für JSON oder CSV Datei fehlt", + parent=self) + return + 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()