Files
Brovski-Adress-Etiketten-Ve…/src/brovski-adress-etiketten-verwaltung.py

334 lines
12 KiB
Python

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("<ButtonRelease-1>", self.mouse_click)
self.table.bind("<Return>", self.enter_button)
self.table.bind("<Double-1>", self.mouse_click_double)
self.root.bind("<Up>", self.focus_table)
self.root.bind("<Down>", 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()