diff --git a/app.py b/app.py index 474ef43..bad5851 100644 --- a/app.py +++ b/app.py @@ -39,6 +39,7 @@ from bestand import (hoechster_zaehler_im_ordner, naechste_freie_nummer, vorhandene_rechnungen) from einzelinstanz import (eigene_pid_merken, einzelinstanz_pruefen, fenster_nach_vorn, laufende_pid_lesen) +import theme from theme import ThemeMixin from korrektur import KorrekturMixin from einstellungen import EinstellungenMixin @@ -202,12 +203,13 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk): # ---------------------------------------------------------------- Kopf def _build_kopf(self): - top = ttk.Frame(self, padding=(12, 10, 12, 4)) + top = ttk.Frame(self, padding=(16, 14, 16, 6)) top.pack(fill="x") # Kunde (links) - kunde = ttk.LabelFrame(top, text="Rechnung an (Kunde)", padding=8) - kunde.grid(row=0, column=0, sticky="nsew", padx=(0, 10)) + kunde = ttk.LabelFrame(top, text="RECHNUNG AN (KUNDE)", padding=14, + style="Karte.TLabelframe") + kunde.grid(row=0, column=0, sticky="nsew", padx=(0, 14)) self.k_anrede = tk.StringVar() self.k_vorname = tk.StringVar() self.k_name = tk.StringVar() # Nachname oder Firma – die Zuordnung im Steuerjournal @@ -223,7 +225,8 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk): ("PLZ / Ort *", self.k_plzort), ] for i, (lbl, var) in enumerate(felder): - ttk.Label(kunde, text=lbl).grid(row=i, column=0, sticky="w", pady=2) + ttk.Label(kunde, text=lbl, style="Karte.TLabel").grid( + row=i, column=0, sticky="w", pady=4) if var is self.k_anrede: # Auswahlliste, aber nicht gesperrt - Sonderfaelle kann man tippen. w = ttk.Combobox(kunde, textvariable=var, width=36, values=ANREDEN) @@ -232,12 +235,13 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk): w.grid(row=i, column=1, sticky="ew", padx=(8, 0), pady=2) ttk.Label(kunde, text="* Pflicht – Name und Anschrift des Kunden gehören " "auf die Rechnung (§ 14 Abs. 4 Nr. 1 UStG).", - foreground=self.col_muted, wraplength=330, justify="left").grid( + style="KarteMuted.TLabel", wraplength=330, justify="left").grid( row=len(felder), column=0, columnspan=2, sticky="w", pady=(6, 0)) kunde.columnconfigure(1, weight=1) # Rechnungsinfos (rechts) - info = ttk.LabelFrame(top, text="Rechnungsdaten", padding=8) + info = ttk.LabelFrame(top, text="RECHNUNGSDATEN", padding=14, + style="Karte.TLabelframe") info.grid(row=0, column=1, sticky="nsew") self.r_nummer = tk.StringVar() self.r_datum = tk.StringVar(value=datetime.date.today().strftime("%d.%m.%Y")) @@ -253,8 +257,8 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk): date_vars = (self.r_datum, self.r_anreise, self.r_abreise) self.datum_labels = {} for i, (lbl, var) in enumerate(rows): - beschriftung = ttk.Label(info, text=lbl) - beschriftung.grid(row=i, column=0, sticky="w", pady=2) + beschriftung = ttk.Label(info, text=lbl, style="Karte.TLabel") + beschriftung.grid(row=i, column=0, sticky="w", pady=4) if var is self.r_anreise: self.datum_labels["von"] = beschriftung elif var is self.r_abreise: @@ -267,14 +271,16 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk): self.nummer_entry = ent if var in date_vars: # Datum: Punkte automatisch, Cursor ans Ende ent.bind("", self._auto_datum_evt) - self.naechte_label = ttk.Label(info, text="Nächte") - self.naechte_label.grid(row=4, column=0, sticky="w", pady=2) - ttk.Label(info, textvariable=self.r_naechte, font=("Segoe UI", 10, "bold")).grid( + self.naechte_label = ttk.Label(info, text="Nächte", style="Karte.TLabel") + self.naechte_label.grid(row=4, column=0, sticky="w", pady=4) + ttk.Label(info, textvariable=self.r_naechte, style="Karte.TLabel", + font=(theme.SCHRIFT_NAME, 11, "bold")).grid( row=4, column=1, sticky="w", padx=(8, 0)) # Storno-Hinweis: steht nur da, solange ein Storno im Zwischenspeicher liegt. self.storno_hinweis = tk.StringVar(value="") - ttk.Label(info, textvariable=self.storno_hinweis, foreground="#b00020", - font=("Segoe UI", 9, "bold"), wraplength=200, justify="left").grid( + ttk.Label(info, textvariable=self.storno_hinweis, style="Karte.TLabel", + foreground=self.col_gefahr, + font=(theme.SCHRIFT_NAME, 9, "bold"), wraplength=200, justify="left").grid( row=5, column=0, columnspan=2, sticky="w", pady=(6, 0)) info.columnconfigure(1, weight=1) self.r_anreise.trace_add("write", lambda *a: self._update_naechte()) @@ -369,10 +375,11 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk): self.blaetter.add(montage_tab, text=" Montage ") self._build_montage(montage_tab) - wrap = ttk.LabelFrame(camping, text="Leistungen – Anzahl (und ggf. Personen) eintragen", padding=(6, 4)) + wrap = ttk.LabelFrame(camping, text="LEISTUNGEN – ANZAHL (UND GGF. PERSONEN) EINTRAGEN", + padding=(12, 8), style="Karte.TLabelframe") wrap.pack(side="top", fill="both", expand=True) - canvas = tk.Canvas(wrap, highlightthickness=0, bg=self.col_bg) + canvas = tk.Canvas(wrap, highlightthickness=0, bg=self.col_card) sb = ttk.Scrollbar(wrap, orient="vertical", command=canvas.yview) self.pos_frame = ttk.Frame(canvas) self._pos_win = canvas.create_window((0, 0), window=self.pos_frame, anchor="nw") @@ -399,10 +406,11 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk): # Kopfzeile (gleiches Grid wie die Daten) for i, (title, anchor, _) in enumerate(self.COLS): - ttk.Label(self.pos_frame, text=title, anchor=anchor, - font=("Segoe UI", 9, "bold")).grid(row=0, column=i, padx=3, pady=(0, 3), sticky="ew") + ttk.Label(self.pos_frame, text=title.upper(), anchor=anchor, + style="Abschnitt.TLabel").grid( + row=0, column=i, padx=3, pady=(0, 5), sticky="ew") ttk.Separator(self.pos_frame, orient="horizontal").grid( - row=1, column=0, columnspan=len(self.COLS), sticky="ew", pady=(0, 2)) + row=1, column=0, columnspan=len(self.COLS), sticky="ew", pady=(0, 4)) for idx, pos in enumerate(self.cfg["katalog"]): r = idx + 2 @@ -414,17 +422,26 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk): av.trace_add("write", lambda *a: self.recalc()) pv.trace_add("write", lambda *a: self.recalc()) - ttk.Label(self.pos_frame, text=str(nr), anchor="center").grid(row=r, column=0, padx=3, pady=2, sticky="ew") + # Jede zweite Zeile bekommt einen anderen Grund: bei 17 Positionen + # verrutscht man beim Eintragen sonst leicht eine Zeile. + zeilenstil = "Zebra.TLabel" if idx % 2 else "Karte.TLabel" + ttk.Label(self.pos_frame, text=str(nr), anchor="center", style=zeilenstil).grid( + row=r, column=0, padx=0, pady=3, sticky="ew", ipady=2) ttk.Label(self.pos_frame, text=pos["leistung"], anchor="w", wraplength=320, - justify="left").grid(row=r, column=1, padx=3, pady=2, sticky="ew") - ttk.Label(self.pos_frame, text=str(pos["satz"]), anchor="center").grid(row=r, column=2, padx=3, pady=2, sticky="ew") - ttk.Entry(self.pos_frame, textvariable=av, width=8, justify="center").grid(row=r, column=3, padx=3, pady=2) + justify="left", style=zeilenstil).grid( + row=r, column=1, padx=0, pady=3, sticky="ew", ipady=2) + ttk.Label(self.pos_frame, text=str(pos["satz"]), anchor="center", + style=zeilenstil).grid(row=r, column=2, padx=0, pady=3, sticky="ew", ipady=2) + ttk.Entry(self.pos_frame, textvariable=av, width=8, justify="center").grid( + row=r, column=3, padx=4, pady=3) # Personen-Feld NUR bei "pro Person"-Positionen anzeigen if pos["pro_person"]: - ttk.Entry(self.pos_frame, textvariable=pv, width=6, justify="center").grid(row=r, column=4, padx=3, pady=2) - ttk.Label(self.pos_frame, text=berechnung.eur_kurz(pos["preis"]), anchor="e").grid(row=r, column=5, padx=3, pady=2, sticky="ew") - g = ttk.Label(self.pos_frame, text="0,00", anchor="e") - g.grid(row=r, column=6, padx=3, pady=2, sticky="ew") + ttk.Entry(self.pos_frame, textvariable=pv, width=6, justify="center").grid( + row=r, column=4, padx=4, pady=3) + ttk.Label(self.pos_frame, text=berechnung.eur_kurz(pos["preis"]), anchor="e", + style=zeilenstil).grid(row=r, column=5, padx=0, pady=3, sticky="ew", ipady=2) + g = ttk.Label(self.pos_frame, text="0,00", anchor="e", style=zeilenstil) + g.grid(row=r, column=6, padx=0, pady=3, sticky="ew", ipady=2) self.gesamt_lbls[nr] = g # ------------------------------------------------------------ Montage @@ -440,7 +457,7 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk): self.m_monate.trace_add("write", lambda *a: self.recalc()) ttk.Label(eltern, text="Monatsmiete für einen Montagearbeiter", - font=("Segoe UI", 11, "bold")).grid(row=0, column=0, columnspan=4, sticky="w") + font=(theme.SCHRIFT_NAME, 11, "bold")).grid(row=0, column=0, columnspan=4, sticky="w") ttk.Label(eltern, wraplength=760, justify="left", foreground=self.col_muted, text="Den Zeitraum trägst du oben rechts ein (dieselben Felder wie Anreise/Abreise). " "Bezeichnung, Steuersatz und Monatspreis stehen unter " @@ -451,17 +468,17 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk): ttk.Entry(eltern, textvariable=self.m_monate, width=8, justify="center").grid( row=2, column=1, sticky="w", padx=(8, 24), pady=3) ttk.Label(eltern, text="Monatspreis (brutto):").grid(row=2, column=2, sticky="w", pady=3) - self.m_preis = ttk.Label(eltern, text="", font=("Segoe UI", 10, "bold")) + self.m_preis = ttk.Label(eltern, text="", font=(theme.SCHRIFT_NAME, 10, "bold")) self.m_preis.grid(row=2, column=3, sticky="w", padx=(8, 0), pady=3) ttk.Separator(eltern, orient="horizontal").grid( row=3, column=0, columnspan=4, sticky="ew", pady=12) ttk.Label(eltern, text="So steht es auf der Rechnung:", - font=("Segoe UI", 9, "bold")).grid(row=4, column=0, columnspan=4, sticky="w") + font=(theme.SCHRIFT_NAME, 9, "bold")).grid(row=4, column=0, columnspan=4, sticky="w") self.m_text = ttk.Label(eltern, text="", wraplength=760, justify="left") self.m_text.grid(row=5, column=0, columnspan=4, sticky="w", pady=(4, 10)) - self.m_zahlen = ttk.Label(eltern, text="", font=("Segoe UI", 10)) + self.m_zahlen = ttk.Label(eltern, text="", font=(theme.SCHRIFT_NAME, 10)) self.m_zahlen.grid(row=6, column=0, columnspan=4, sticky="w") eltern.columnconfigure(3, weight=1) @@ -522,14 +539,14 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk): status = ttk.Frame(self, padding=(12, 2)) status.pack(side="bottom", fill="x") ttk.Label(status, text="powered by ravokk", foreground=self.col_muted, - font=("Segoe UI", 8)).pack(side="right") + font=(theme.SCHRIFT_NAME, 8)).pack(side="right") # Steht der gemeinsame Ordner? Das gehoert sichtbar hin - ohne ihn kennt # das Programm die Nummern des Steuerjournals nicht. self.buch_lbl = ttk.Label(status, text="", foreground=self.col_muted, - font=("Segoe UI", 8)) + font=(theme.SCHRIFT_NAME, 8)) self.buch_lbl.pack(side="left") - f = ttk.Frame(self, padding=(12, 6, 12, 10)) + f = ttk.Frame(self, padding=(16, 12, 16, 14)) f.pack(side="bottom", fill="x") ttk.Separator(self, orient="horizontal").pack(side="bottom", fill="x") @@ -539,13 +556,17 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk): links = ttk.Frame(f) links.pack(side="left") - ttk.Button(links, text="Vorschau", command=self.vorschau_zeigen, width=12).pack(side="left", padx=4) - ttk.Button(links, text="PDF erstellen", command=self.pdf_erstellen, width=18).pack(side="left", padx=4) + # "PDF erstellen" ist der Knopf, um den es geht - der traegt die + # Akzentfarbe. Storno greift in eine ausgestellte Rechnung ein und ist + # deshalb rot markiert, nicht weil er gefaehrlich waere, sondern damit + # niemand ihn versehentlich statt "PDF erstellen" trifft. + ttk.Button(links, text="PDF erstellen", command=self.pdf_erstellen, + style="Haupt.TButton").pack(side="left", padx=(0, 6)) + ttk.Button(links, text="Vorschau", command=self.vorschau_zeigen).pack(side="left", padx=6) ttk.Button(links, text="Storno", command=self.dlg_storno, - width=12).pack(side="left", padx=4) - ttk.Button(links, text="Berichtigen", command=self.dlg_berichtigen, - width=14).pack(side="left", padx=4) - ttk.Button(links, text="Zurücksetzen", command=self.reset_formular, width=14).pack(side="left", padx=4) + style="Gefahr.TButton").pack(side="left", padx=6) + ttk.Button(links, text="Berichtigen", command=self.dlg_berichtigen).pack(side="left", padx=6) + ttk.Button(links, text="Zurücksetzen", command=self.reset_formular).pack(side="left", padx=6) # --------------------------------------------------- Gemeinsames Buch def _buch_oeffnen(self): @@ -638,7 +659,7 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk): rahmen = ttk.Frame(d, padding=18) rahmen.pack(fill="both", expand=True) ttk.Label(rahmen, text="Wo soll die Rechnungsnummer weiterlaufen?", - font=("Segoe UI", 13, "bold")).grid(row=0, column=0, columnspan=2, sticky="w") + font=(theme.SCHRIFT_NAME, 13, "bold")).grid(row=0, column=0, columnspan=2, sticky="w") ttk.Label(rahmen, wraplength=620, justify="left", text=( "Zwei Wege - such dir einen aus." + chr(10) + chr(10) + "Das wird EINMAL eingerichtet. Danach steht der Zähler fest: eine " @@ -649,7 +670,7 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk): if gefunden: tk.Label(rahmen, wraplength=620, justify="left", fg="#1f5c3d", - font=("Segoe UI", 9, "bold"), + font=(theme.SCHRIFT_NAME, 9, "bold"), text=f"Im Rechnungsordner und im Nummernbuch steht schon " f"{jahr}-{gefunden:03d} – niedriger geht es nicht.").grid( row=2, column=0, columnspan=2, sticky="w", pady=(0, 10)) @@ -677,7 +698,7 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk): eingabe.pack(side="left", padx=(8, 0)) weg_gewaehlt() hinweis = tk.Label(rahmen, text="", wraplength=620, justify="left", fg="#a00000", - font=("Segoe UI", 9)) + font=(theme.SCHRIFT_NAME, 9)) hinweis.grid(row=5, column=0, columnspan=2, sticky="w", pady=(8, 0)) def uebernehmen(): @@ -957,7 +978,8 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk): rows.append(("Gesamtbetrag:", berechnung.eur(r.gesamt), True)) for i, (lbl, val, fett) in enumerate(rows): - font = ("Segoe UI", 12, "bold") if fett else ("Segoe UI", 10) + font = ((theme.SCHRIFT_NAME, 15, "bold") if fett + else (theme.SCHRIFT_NAME, 10)) pady = (5, 0) if fett else (1, 0) ttk.Label(self.summen_box, text=lbl, font=font).grid(row=i, column=0, sticky="e", padx=6, pady=pady) vkw = {"font": font, "width": 14, "anchor": "e"} @@ -1040,7 +1062,7 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk): rahmen = ttk.Frame(d, padding=18) rahmen.pack(fill="both", expand=True) ttk.Label(rahmen, text=f"{wer or 'ohne Namen'} – {betrag}", - font=("Segoe UI", 12, "bold")).grid(row=0, column=0, sticky="w") + font=(theme.SCHRIFT_NAME, 12, "bold")).grid(row=0, column=0, sticky="w") ttk.Label(rahmen, wraplength=560, justify="left", text=( f"RECHNUNG {nummer} – der Beleg. Danach lässt sie sich nicht mehr " diff --git a/assets/fonts/Roboto-Italic.ttf b/assets/fonts/Roboto-Italic.ttf new file mode 100644 index 0000000..a122c13 Binary files /dev/null and b/assets/fonts/Roboto-Italic.ttf differ diff --git a/assets/fonts/Roboto.ttf b/assets/fonts/Roboto.ttf new file mode 100644 index 0000000..5522a36 Binary files /dev/null and b/assets/fonts/Roboto.ttf differ diff --git a/einstellungen.py b/einstellungen.py index 176de0c..794098f 100644 --- a/einstellungen.py +++ b/einstellungen.py @@ -11,6 +11,7 @@ import tkinter as tk from tkinter import ttk, messagebox, filedialog, colorchooser import berechnung +import theme import bilder import config from helfer import parse_num @@ -50,7 +51,7 @@ class EinstellungenMixin: self._style_dialog(d) frm = ttk.Frame(d, padding=18) frm.pack(fill="both", expand=True) - ttk.Label(frm, text="Rechnungstool Campinghof Bartl", font=("Segoe UI", 11, "bold"), + ttk.Label(frm, text="Rechnungstool Campinghof Bartl", font=(theme.SCHRIFT_NAME, 11, "bold"), foreground=self.col_accent).pack(anchor="w") ttk.Label(frm, justify="left", text="Daten eingeben, PDF erstellen, drucken.\n" @@ -74,7 +75,7 @@ class EinstellungenMixin: frm = ttk.Frame(d, padding=12) frm.pack(fill="both", expand=True) - ttk.Label(frm, wraplength=580, justify="left", font=("Segoe UI", 9, "bold"), + ttk.Label(frm, wraplength=580, justify="left", font=(theme.SCHRIFT_NAME, 9, "bold"), text="Steuersätze: Satz % + Name. Die Katalog-Preise sind BRUTTO; die USt dieses " "Satzes wird herausgerechnet und als 'enthalten' ausgewiesen. Haken bei " "'Aufschlag': diese Steuer (z.B. Beherbergungssteuer) wird stattdessen auf den " @@ -92,7 +93,7 @@ class EinstellungenMixin: for w in liste.winfo_children(): w.destroy() for c, t in enumerate(["Satz %", "Bezeichnung (Name der Steuer)", "Aufschlag", ""]): - ttk.Label(liste, text=t, font=("Segoe UI", 9, "bold")).grid( + ttk.Label(liste, text=t, font=(theme.SCHRIFT_NAME, 9, "bold")).grid( row=0, column=c, padx=4, pady=(0, 3), sticky="w") for i, row in enumerate(arbeit, start=1): ttk.Entry(liste, textvariable=row["satz"], width=8, justify="center").grid(row=i, column=0, padx=4, pady=2) @@ -247,7 +248,7 @@ class EinstellungenMixin: for w in inner.winfo_children(): w.destroy() for c, t in enumerate(["Pos.", "Bezeichnung", "Steuersatz", "Preis €", "pro Person", ""]): - ttk.Label(inner, text=t, font=("Segoe UI", 9, "bold")).grid( + ttk.Label(inner, text=t, font=(theme.SCHRIFT_NAME, 9, "bold")).grid( row=0, column=c, padx=4, pady=(0, 4), sticky="w") for i, row in enumerate(arbeit, start=1): ttk.Label(inner, text=str(i), width=4).grid(row=i, column=0, padx=4, pady=1) diff --git a/korrektur.py b/korrektur.py index d0d4ccb..4bd8a1a 100644 --- a/korrektur.py +++ b/korrektur.py @@ -23,6 +23,7 @@ import tkinter as tk from tkinter import ttk, messagebox import berechnung +import theme import config import pdf_renderer import berechnung @@ -375,7 +376,7 @@ class KorrekturMixin: rahmen = ttk.Frame(d, padding=18) rahmen.pack(fill="both", expand=True) ttk.Label(rahmen, text=f"Storno zu Rechnung {alt_nummer}", - font=("Segoe UI", 13, "bold")).pack(anchor="w") + font=(theme.SCHRIFT_NAME, 13, "bold")).pack(anchor="w") # ---- Schritt 1: A oder B ------------------------------------------ schritt1 = ttk.Frame(rahmen) @@ -384,17 +385,17 @@ class KorrekturMixin: "Auf dem Storno-Blatt muss stehen, wie die Leistung stattdessen abgerechnet " "wird – sonst weiß der Gast nicht, woran er ist.")).pack(anchor="w", pady=(0, 12)) ttk.Label(schritt1, text="A – es folgt eine neue Rechnung", - font=("Segoe UI", 10, "bold")).pack(anchor="w") + font=(theme.SCHRIFT_NAME, 10, "bold")).pack(anchor="w") ttk.Label(schritt1, wraplength=620, justify="left", text=( "Der übliche Weg: die Rechnung war falsch und wird nach dem Storno richtig neu " "geschrieben. Das Formular bleibt danach offen.")).pack(anchor="w", pady=(2, 10)) ttk.Label(schritt1, text="B – die Leistung ist schon abgerechnet", - font=("Segoe UI", 10, "bold")).pack(anchor="w") + font=(theme.SCHRIFT_NAME, 10, "bold")).pack(anchor="w") ttk.Label(schritt1, wraplength=620, justify="left", text=( "Für Doppelte: derselbe Aufenthalt steht mehrfach im Ordner. Im nächsten Schritt " "trägst du die Nummer der Rechnung ein, die bleibt.")).pack(anchor="w", pady=(2, 10)) ttk.Label(schritt1, text="C – nur Storno", - font=("Segoe UI", 10, "bold")).pack(anchor="w") + font=(theme.SCHRIFT_NAME, 10, "bold")).pack(anchor="w") ttk.Label(schritt1, wraplength=620, justify="left", text=( "Die Leistung wird gar nicht abgerechnet: Buchung geplatzt oder die Rechnung " "war komplett irrtümlich. Es folgt keine neue Rechnung und es gibt auch keine " @@ -411,7 +412,7 @@ class KorrekturMixin: feld = ttk.Entry(zeile, textvariable=eingabe, width=16, justify="center") feld.pack(side="left", padx=(8, 0)) stand = tk.Label(schritt2, text="", wraplength=620, justify="left", - font=("Segoe UI", 9)) + font=(theme.SCHRIFT_NAME, 9)) stand.pack(anchor="w", pady=(8, 0)) def suchen(*_a): diff --git a/ordnerwahl.py b/ordnerwahl.py index 616cab6..060ff7b 100644 --- a/ordnerwahl.py +++ b/ordnerwahl.py @@ -25,6 +25,8 @@ import os import tkinter as tk from tkinter import ttk, filedialog, messagebox +import theme + import gemeinsam BREITE = 780 @@ -39,6 +41,18 @@ class OrdnerWahl(tk.Toplevel): self.ergebnis = None self.title("Gemeinsamer Ordner für das Nummernbuch") self.transient(master) + # Dieses Fenster stand als einziges noch in Weiss da: es kam nie durch + # _style_dialog. Farben holt es sich jetzt vom Hauptfenster, damit es + # nicht wieder auseinanderlaeuft, wenn dort etwas geaendert wird. + self._bg = getattr(master, "col_bg", "#0d0e10") + self._fg = getattr(master, "col_fg", "#e8e8ea") + self._warn = getattr(master, "col_gefahr", "#e85a5a") + self._gut = getattr(master, "col_accent", "#06C6A4") + self._muted = getattr(master, "col_muted", "#8a8e96") + if hasattr(master, "_style_dialog"): + master._style_dialog(self) + else: + self.configure(bg=self._bg) self.pfad = tk.StringVar(value=vorgabe or "") rahmen = ttk.Frame(self, padding=18) @@ -47,7 +61,7 @@ class OrdnerWahl(tk.Toplevel): breite = BREITE - 60 ttk.Label(rahmen, text="Bitte den gemeinsamen Ordner auswählen", - font=("Segoe UI", 13, "bold")).grid(row=0, column=0, sticky="w") + font=(theme.SCHRIFT_NAME, 13, "bold")).grid(row=0, column=0, sticky="w") ttk.Label(rahmen, wraplength=breite, justify="left", text=( "Rechnungstool und Steuerjournal führen zusammen ein Nummernbuch. Darin steht " "jede vergebene Rechnungsnummer – auch die aus der alten Excel-Tabelle, zu der " @@ -56,8 +70,8 @@ class OrdnerWahl(tk.Toplevel): "eine Nummer ein zweites Mal. Jede Nummer darf es aber nur einmal geben " "(§ 14 Abs. 4 Nr. 4 UStG).")).grid(row=1, column=0, sticky="w", pady=(8, 12)) - tk.Label(rahmen, wraplength=breite, justify="left", fg="#a00000", - font=("Segoe UI", 10, "bold"), + tk.Label(rahmen, wraplength=breite, justify="left", fg=self._warn, bg=self._bg, + font=(theme.SCHRIFT_NAME, 10, "bold"), text="WICHTIG: In BEIDEN Programmen muss derselbe Ordner stehen – am besten " "der Ordner, in dem die Programme liegen.").grid( row=2, column=0, sticky="w", pady=(0, 14)) @@ -71,7 +85,8 @@ class OrdnerWahl(tk.Toplevel): width=26).grid(row=0, column=1, sticky="w", padx=(10, 0)) self.stand = tk.Label(rahmen, text="", wraplength=breite, justify="left", - font=("Segoe UI", 9)) + bg=self._bg, + font=(theme.SCHRIFT_NAME, 9)) self.stand.grid(row=4, column=0, sticky="w", pady=(10, 0)) knoepfe = ttk.Frame(rahmen) @@ -127,12 +142,12 @@ class OrdnerWahl(tk.Toplevel): """ ordner = self.pfad.get().strip() if not ordner: - self.stand.configure(text="Noch kein Ordner gewählt.", fg="#a00000") + self.stand.configure(text="Noch kein Ordner gewählt.", fg=self._warn) self.ok.state(["disabled"]) return taugt, grund = gemeinsam.ordner_taugt(ordner) if not taugt: - self.stand.configure(text=grund, fg="#a00000") + self.stand.configure(text=grund, fg=self._warn) self.ok.state(["disabled"]) return self.ok.state(["!disabled"]) @@ -144,11 +159,11 @@ class OrdnerWahl(tk.Toplevel): self.stand.configure( text=f"Hier liegt schon ein Nummernbuch mit {anzahl} Rechnungsnummern – " "das ist der Ordner des anderen Programms. Genau richtig.", - fg="#1f5c3d") + fg=self._gut) else: self.stand.configure( text="Hier ist noch kein Nummernbuch – es wird neu angelegt. Wähle im " - "anderen Programm denselben Ordner.", fg="#444444") + "anderen Programm denselben Ordner.", fg=self._muted) def uebernehmen(self): ordner = self.pfad.get().strip() diff --git a/pruef_angebot.py b/pruef_angebot.py index e7229f2..52489cc 100644 --- a/pruef_angebot.py +++ b/pruef_angebot.py @@ -63,6 +63,10 @@ shutil.copy(os.path.join(QUELLE, "config.json"), os.path.join(tmp, "config.json" config.CONFIG_PATH = os.path.join(tmp, "config.json") roh = json.load(open(config.CONFIG_PATH, encoding="utf-8")) roh["rechnung_jahr"], roh["rechnung_zaehler"] = 2026, 0 +# Der Erststart-Zustand gehoert dem Pruefstand, nicht der config des +# Entwicklerrechners: sonst laeuft er in einen anderen Zweig, sobald hier +# jemand die Nummer einmal eingerichtet hat. +roh["nummer_eingerichtet"] = False roh["output_dir"] = ordner json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False, indent=2) diff --git a/pruef_api.py b/pruef_api.py index ac9d217..01a4fc2 100644 --- a/pruef_api.py +++ b/pruef_api.py @@ -39,6 +39,10 @@ config.BASE_DIR = tmp config.CONFIG_PATH = os.path.join(tmp, "config.json") roh = json.load(open(config.CONFIG_PATH, encoding="utf-8")) roh["rechnung_jahr"], roh["rechnung_zaehler"] = 2026, 0 +# Der Erststart-Zustand gehoert dem Pruefstand, nicht der config des +# Entwicklerrechners: sonst laeuft er in einen anderen Zweig, sobald hier +# jemand die Nummer einmal eingerichtet hat. +roh["nummer_eingerichtet"] = False roh["output_dir"] = ordner roh["gemeinsam_ordner"] = gemeinsam_ordner json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False, indent=2) diff --git a/pruef_dialoge.py b/pruef_dialoge.py index bac300b..ec07f7f 100644 --- a/pruef_dialoge.py +++ b/pruef_dialoge.py @@ -27,6 +27,10 @@ shutil.copy(os.path.join(QUELLE, "config.json"), os.path.join(tmp, "config.json" config.CONFIG_PATH = os.path.join(tmp, "config.json") roh = json.load(open(config.CONFIG_PATH, encoding="utf-8")) roh["output_dir"] = ordner +# Der Erststart-Zustand gehoert dem Pruefstand, nicht der config des +# Entwicklerrechners: dlg_nummer zeigt sonst nur eine Meldung statt des +# Fensters, das hier geprueft werden soll. +roh["nummer_eingerichtet"] = False json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False) # Kein Erststart-Dialog im Pruefstand - er wuerde den Lauf haengen lassen. diff --git a/pruef_kernregeln.py b/pruef_kernregeln.py index 5fc8eda..47e9638 100644 --- a/pruef_kernregeln.py +++ b/pruef_kernregeln.py @@ -34,6 +34,10 @@ shutil.copy(os.path.join(QUELLE, "config.json"), os.path.join(tmp, "config.json" config.CONFIG_PATH = os.path.join(tmp, "config.json") roh = json.load(open(config.CONFIG_PATH, encoding="utf-8")) roh["rechnung_jahr"], roh["rechnung_zaehler"] = 2026, 0 +# Der Erststart-Zustand gehoert dem Pruefstand, nicht der config des +# Entwicklerrechners: sonst laeuft er in einen anderen Zweig, sobald hier +# jemand die Nummer einmal eingerichtet hat. +roh["nummer_eingerichtet"] = False roh["output_dir"] = ordner json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False) diff --git a/pruef_kette.py b/pruef_kette.py index 3bb28fe..8ec6709 100644 --- a/pruef_kette.py +++ b/pruef_kette.py @@ -33,6 +33,10 @@ shutil.copy(os.path.join(QUELLE, "config.json"), os.path.join(tmp, "config.json" config.CONFIG_PATH = os.path.join(tmp, "config.json") roh = json.load(open(config.CONFIG_PATH, encoding="utf-8")) roh["rechnung_jahr"], roh["rechnung_zaehler"] = 2026, 0 +# Der Erststart-Zustand gehoert dem Pruefstand, nicht der config des +# Entwicklerrechners: sonst laeuft er in einen anderen Zweig, sobald hier +# jemand die Nummer einmal eingerichtet hat. +roh["nummer_eingerichtet"] = False roh["output_dir"] = ordner json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False) diff --git a/pruef_montage.py b/pruef_montage.py index 86581c1..54928b8 100644 --- a/pruef_montage.py +++ b/pruef_montage.py @@ -37,6 +37,10 @@ shutil.copy(os.path.join(QUELLE, "config.json"), os.path.join(tmp, "config.json" config.CONFIG_PATH = os.path.join(tmp, "config.json") roh = json.load(open(config.CONFIG_PATH, encoding="utf-8")) roh["rechnung_jahr"], roh["rechnung_zaehler"] = 2026, 0 +# Der Erststart-Zustand gehoert dem Pruefstand, nicht der config des +# Entwicklerrechners: sonst laeuft er in einen anderen Zweig, sobald hier +# jemand die Nummer einmal eingerichtet hat. +roh["nummer_eingerichtet"] = False roh["output_dir"] = ordner json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False, indent=2) diff --git a/pruef_nummernbuch.py b/pruef_nummernbuch.py index e857f84..393e9ea 100644 --- a/pruef_nummernbuch.py +++ b/pruef_nummernbuch.py @@ -100,6 +100,10 @@ shutil.copy(os.path.join(TOOL, "config.json"), os.path.join(tmp, "config.json")) config.CONFIG_PATH = os.path.join(tmp, "config.json") roh = json.load(open(config.CONFIG_PATH, encoding="utf-8")) roh["rechnung_jahr"], roh["rechnung_zaehler"] = 2026, 0 +# Der Erststart-Zustand gehoert dem Pruefstand, nicht der config des +# Entwicklerrechners: sonst laeuft er in einen anderen Zweig, sobald hier +# jemand die Nummer einmal eingerichtet hat. +roh["nummer_eingerichtet"] = False roh["output_dir"] = rechnungen roh["gemeinsam_ordner"] = gemeinsam_ordner json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False, indent=2) diff --git a/pruef_nummernsperre.py b/pruef_nummernsperre.py index 5470554..4e99b5b 100644 --- a/pruef_nummernsperre.py +++ b/pruef_nummernsperre.py @@ -55,6 +55,10 @@ shutil.copy(os.path.join(QUELLE, "config.json"), os.path.join(tmp, "config.json" config.CONFIG_PATH = os.path.join(tmp, "config.json") roh = json.load(open(config.CONFIG_PATH, encoding="utf-8")) roh["rechnung_jahr"], roh["rechnung_zaehler"] = 2026, 0 +# Der Erststart-Zustand gehoert dem Pruefstand, nicht der config des +# Entwicklerrechners: steht dort "eingerichtet", zeigt dlg_nummer nur eine +# Meldung statt des Fensters, das hier bedient werden soll. +roh["nummer_eingerichtet"] = False roh["output_dir"] = ordner json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False, indent=2) diff --git a/pruef_storno.py b/pruef_storno.py index 232279b..2288a6f 100644 --- a/pruef_storno.py +++ b/pruef_storno.py @@ -29,6 +29,10 @@ shutil.copy(os.path.join(QUELLE, "config.json"), os.path.join(tmp, "config.json" config.CONFIG_PATH = os.path.join(tmp, "config.json") roh = json.load(open(config.CONFIG_PATH, encoding="utf-8")) roh["rechnung_jahr"], roh["rechnung_zaehler"] = 2026, 0 +# Der Erststart-Zustand gehoert dem Pruefstand, nicht der config des +# Entwicklerrechners: sonst laeuft er in einen anderen Zweig, sobald hier +# jemand die Nummer einmal eingerichtet hat. +roh["nummer_eingerichtet"] = False roh["output_dir"] = ordner json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False, indent=2) diff --git a/pruef_vorschau.py b/pruef_vorschau.py index fa615e7..f2917ce 100644 --- a/pruef_vorschau.py +++ b/pruef_vorschau.py @@ -30,6 +30,10 @@ config.BASE_DIR = tmp config.CONFIG_PATH = os.path.join(tmp, "config.json") roh = json.load(open(config.CONFIG_PATH, encoding="utf-8")) roh["rechnung_jahr"], roh["rechnung_zaehler"] = 2026, 0 +# Der Erststart-Zustand gehoert dem Pruefstand, nicht der config des +# Entwicklerrechners: sonst laeuft er in einen anderen Zweig, sobald hier +# jemand die Nummer einmal eingerichtet hat. +roh["nummer_eingerichtet"] = False roh["output_dir"] = ordner json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False) diff --git a/theme.py b/theme.py index dd06109..b1b26a3 100644 --- a/theme.py +++ b/theme.py @@ -7,11 +7,85 @@ baut das Programm Menueleiste und Dropdowns selbst; hier stehen nur die Farben. import sys import tkinter as tk +from tkinter import font as tkfont from tkinter import ttk import config +# Schrift: Roboto, so wie in den Web-Oberflaechen. Sie wird NICHT auf dem +# Rechner installiert, sondern nur fuer diesen einen Prozess geladen (die +# Windows-Funktion AddFontResourceEx mit FR_PRIVATE). Damit braucht niemand +# Adminrechte, und nach dem Schliessen ist der Rechner wieder wie vorher. +# Findet sich die Datei nicht, bleibt es bei Segoe UI - das Programm muss auch +# ohne mitgelieferte Schrift laufen. +SCHRIFT_NAME = "Segoe UI" +SCHRIFT = (SCHRIFT_NAME, 10) +KLEIN = (SCHRIFT_NAME, 9) +_schrift_geladen = False + + +def schrift_vorbereiten(): + """Roboto laden und zur Schrift des Programms machen. Gibt den Namen zurueck.""" + global SCHRIFT_NAME, SCHRIFT, KLEIN, _schrift_geladen + if _schrift_geladen: + return SCHRIFT_NAME + _schrift_geladen = True + if not sys.platform.startswith("win"): + return SCHRIFT_NAME + import ctypes + import os + FR_PRIVATE = 0x10 + geladen = 0 + for basis in (config.BASE_DIR, config.RESOURCE_DIR, os.path.dirname(os.path.abspath(__file__))): + ordner = os.path.join(basis, "assets", "fonts") + if not os.path.isdir(ordner): + continue + for datei in sorted(os.listdir(ordner)): + if datei.lower().endswith((".ttf", ".otf")): + geladen += ctypes.windll.gdi32.AddFontResourceExW( + os.path.join(ordner, datei), FR_PRIVATE, 0) + if geladen: + break + if not geladen: + print("[schrift] Roboto nicht gefunden - bleibe bei Segoe UI") + return SCHRIFT_NAME + # Ob Windows sie wirklich angenommen hat, verraet erst die Schriftenliste. + if "Roboto" not in tkfont.families(): + print("[schrift] Roboto liess sich nicht laden - bleibe bei Segoe UI") + return SCHRIFT_NAME + SCHRIFT_NAME = "Roboto" + SCHRIFT = (SCHRIFT_NAME, 10) + KLEIN = (SCHRIFT_NAME, 9) + for name in ("TkDefaultFont", "TkTextFont", "TkMenuFont", "TkHeadingFont", + "TkIconFont", "TkTooltipFont"): + try: + tkfont.nametofont(name).configure(family=SCHRIFT_NAME, size=10) + except tk.TclError: + pass + print("[schrift] Roboto geladen") + return SCHRIFT_NAME + + +def mischen(vorn, hinten, anteil): + """Wie 'rgba(vorn, anteil)' auf 'hinten' - nur ausgerechnet statt gemalt. + + Tk kann keine halbdurchsichtigen Flaechen (kein backdrop-filter, keine + Alpha-Kanaele auf Widgets). Seine Web-Oberflaechen bauen den Glas-Effekt aus + genau zwei Zutaten: eine getoente Flaeche und ein feiner Rand in + Akzent-Transparenz. Beides laesst sich vorher ausrechnen - das Ergebnis sieht + identisch aus, solange der Grund einfarbig ist. Und der ist es hier. + """ + v = vorn.lstrip("#") + h = hinten.lstrip("#") + teile = [] + for i in (0, 2, 4): + a = int(v[i:i + 2], 16) + b = int(h[i:i + 2], 16) + teile.append(round(a * anteil + b * (1 - anteil))) + return "#%02x%02x%02x" % tuple(teile) + + def windows_dark_mode(): """True, wenn Windows auf das dunkle App-Theme eingestellt ist.""" if not sys.platform.startswith("win"): @@ -32,26 +106,42 @@ class ThemeMixin: def _apply_theme(self): """Farben passend zum Windows-Theme (hell/dunkel) setzen.""" + schrift_vorbereiten() # Roboto, sonst Segoe UI dark = windows_dark_mode() self._dark_mode = dark + # Die Farben sind die aus seinen anderen Sachen (FiveM-Oberflaechen, + # Adminpage, Webseiten): Tuerkis #06C6A4 auf sehr dunklen, gestuften + # Flaechen. Das PROGRAMM traegt diese Handschrift - die RECHNUNG traegt + # die Farbe der jeweiligen Firma (Einstellungen -> Farbe der Rechnung). + # Beides zu vermischen waere falsch: das Blatt gehoert dem Kunden. if dark: - self.col_bg = "#23272e" # Fenster-Hintergrund - self.col_fg = "#e6e6e6" # Schrift - self.col_field = "#2f343c" # Eingabefelder - self.col_sel = "#3a4a44" # aktiv/hover - self.col_accent = "#5fb98f" # Akzent (Gruen, hell) - self.col_line = "#3a3f47" - self.col_scroll = "#4a505a" # Scroll-Griff - self.col_muted = "#8a929c" # gedaempfte Schrift (z.B. "powered by") + self.col_bg = "#0d0e10" # Fenster-Hintergrund (tiefstes Grau) + self.col_card = "#15171b" # Kachel / Abschnitt + self.col_fg = "#e8e8ea" # Schrift + self.col_field = "#1d2025" # Eingabefelder + self.col_sel = "#1f3d38" # aktiv/hover (Akzent, stark abgedunkelt) + self.col_accent = "#06C6A4" # Akzent (Pcore-Tuerkis) + self.col_accent_hell = "#08dbb5" + self.col_auf_accent = "#08221d" # Schrift AUF der Akzentflaeche + self.col_gefahr = "#e85a5a" + self.col_line = "#25282e" + self.col_scroll = "#2f333a" + self.col_muted = "#8a8e96" + self.col_zebra = "#1b1e24" # jede zweite Zeile der Liste else: - self.col_bg = "#f3f3f3" - self.col_fg = "#1a1a1a" + self.col_bg = "#eef0f3" + self.col_card = "#ffffff" + self.col_fg = "#14161a" self.col_field = "#ffffff" - self.col_sel = "#cfe3d8" - self.col_accent = "#1f5c3d" - self.col_line = "#cccccc" - self.col_scroll = "#b8b8b8" - self.col_muted = "#777777" # gedaempfte Schrift (z.B. "powered by") + self.col_sel = "#d7f5ee" + self.col_accent = "#04886f" # dunkler, damit es auf Weiss lesbar ist + self.col_accent_hell = "#06C6A4" + self.col_auf_accent = "#ffffff" + self.col_gefahr = "#c0392b" + self.col_line = "#d8dce1" + self.col_scroll = "#c2c6cc" + self.col_muted = "#5c626b" + self.col_zebra = "#e9ecf0" self.configure(bg=self.col_bg) style = ttk.Style(self) @@ -64,17 +154,89 @@ class ThemeMixin: style.configure(".", background=bg, foreground=fg, fieldbackground=field, bordercolor=self.col_line, lightcolor=bg, darkcolor=bg) style.configure("TFrame", background=bg) - style.configure("TLabel", background=bg, foreground=fg) + style.configure("TLabel", background=bg, foreground=fg, font=SCHRIFT) style.configure("TLabelframe", background=bg, bordercolor=self.col_line) style.configure("TLabelframe.Label", background=bg, foreground=accent) - style.configure("TButton", background=field, foreground=fg, bordercolor=self.col_line) - style.map("TButton", background=[("active", sel)], foreground=[("active", fg)]) + + # --- Kacheln: ein Abschnitt ist eine Flaeche mit Rand, keine Linie --- + style.configure("Karte.TFrame", background=self.col_card, + bordercolor=self.col_line, relief="solid", borderwidth=1) + # Ein Abschnitt ist eine Kachel: eigene Flaeche, feiner Rand, die + # Ueberschrift in Akzentfarbe - genau wie die Kacheln in den + # Web-Oberflaechen. + style.configure("Karte.TLabelframe", background=self.col_card, + bordercolor=self.col_line, relief="solid", borderwidth=1, + lightcolor=self.col_card, darkcolor=self.col_card) + style.configure("Karte.TLabelframe.Label", background=self.col_card, + foreground=accent, font=(SCHRIFT_NAME, 9, "bold")) + style.configure("Karte.TEntry", fieldbackground=field, foreground=fg) + style.configure("Karte.TLabel", background=self.col_card, foreground=fg, font=SCHRIFT) + style.configure("KarteMuted.TLabel", background=self.col_card, + foreground=self.col_muted, font=KLEIN) + # Ueberschrift eines Abschnitts: klein, gross geschrieben, in Akzentfarbe - + # so wie die Kachelkoepfe in den Web-Oberflaechen. + style.configure("Abschnitt.TLabel", background=self.col_card, + foreground=accent, font=(SCHRIFT_NAME, 9, "bold")) + style.configure("Wert.TLabel", background=self.col_card, foreground=fg, + font=(SCHRIFT_NAME, 10)) + style.configure("Summe.TLabel", background=bg, foreground=accent, + font=(SCHRIFT_NAME, 15, "bold")) + style.configure("Zebra.TLabel", background=self.col_zebra, foreground=fg, font=SCHRIFT) + + # --- Knoepfe im Glas-Stil (wie in seinen Oberflaechen) --- + # Getoente Flaeche, feiner Rand in Akzent-Transparenz, Schrift in der + # Akzentfarbe. Kein Vollton: der wirkt in dieser Palette wie ein + # Fremdkoerper. + glas = mischen(accent, bg, 0.10) # ruhige Flaeche + glas_hover = mischen(accent, bg, 0.20) + rand = mischen(accent, bg, 0.35) + rand_hell = mischen(accent, bg, 0.55) + style.configure("TButton", background=glas, foreground=fg, + bordercolor=rand, padding=(14, 7), font=SCHRIFT, + focuscolor=rand, relief="solid", borderwidth=1, + lightcolor=rand, darkcolor=rand) + style.map("TButton", + background=[("pressed", mischen(accent, bg, 0.28)), ("active", glas_hover)], + bordercolor=[("active", rand_hell)], + lightcolor=[("active", rand_hell)], darkcolor=[("active", rand_hell)], + foreground=[("disabled", self.col_muted)]) + + # Der Knopf, um den es geht: dieselbe Glasflaeche, aber kraeftiger + # getoent, mit Akzentrand und Akzentschrift. + haupt = mischen(accent, bg, 0.22) + style.configure("Haupt.TButton", background=haupt, + foreground=self.col_accent_hell, bordercolor=self.col_accent, + lightcolor=self.col_accent, darkcolor=self.col_accent, + relief="solid", borderwidth=1, + padding=(18, 8), font=(SCHRIFT_NAME, 10, "bold")) + style.map("Haupt.TButton", + background=[("pressed", mischen(accent, bg, 0.45)), + ("active", mischen(accent, bg, 0.34)), + ("disabled", bg)], + foreground=[("disabled", self.col_muted)]) + + # Storno greift in eine ausgestellte Rechnung ein - gleicher Stil, rot. + g_rot = mischen(self.col_gefahr, bg, 0.10) + style.configure("Gefahr.TButton", background=g_rot, foreground=self.col_gefahr, + bordercolor=mischen(self.col_gefahr, bg, 0.35), + lightcolor=mischen(self.col_gefahr, bg, 0.35), + darkcolor=mischen(self.col_gefahr, bg, 0.35), + relief="solid", borderwidth=1, + padding=(14, 7), font=SCHRIFT) + style.map("Gefahr.TButton", + background=[("pressed", mischen(self.col_gefahr, bg, 0.30)), + ("active", mischen(self.col_gefahr, bg, 0.20))], + bordercolor=[("active", mischen(self.col_gefahr, bg, 0.55))], + lightcolor=[("active", mischen(self.col_gefahr, bg, 0.55))], + darkcolor=[("active", mischen(self.col_gefahr, bg, 0.55))]) style.configure("Menu.TMenubutton", background=bg, foreground=fg, relief="flat", arrowcolor=bg, padding=(10, 3)) style.map("Menu.TMenubutton", background=[("active", sel)], foreground=[("active", fg)]) style.configure("TCheckbutton", background=bg, foreground=fg) style.map("TCheckbutton", background=[("active", bg)], foreground=[("active", fg)]) - style.configure("TEntry", fieldbackground=field, foreground=fg, insertcolor=fg) + style.configure("TEntry", fieldbackground=field, foreground=fg, insertcolor=fg, + bordercolor=self.col_line, padding=4) + style.map("TEntry", bordercolor=[("focus", accent)]) # Bei state="readonly" zeichnet die Combobox ihren Text als MARKIERUNG, # sobald sie den Fokus hat - ohne diese beiden Farben steht der Wert # unsichtbar im Feld. @@ -101,15 +263,17 @@ class ThemeMixin: # ist. Der offene Reiter bekommt darum den Hintergrund des Blattes und die # Akzentfarbe, die ruhenden bleiben zurueckgenommen. style.configure("TNotebook", background=bg, bordercolor=self.col_line, - tabmargins=(2, 4, 2, 0)) - style.configure("TNotebook.Tab", background=field, foreground=self.col_muted, - bordercolor=self.col_line, padding=(16, 7), - font=("Segoe UI", 10)) + tabmargins=(2, 4, 2, 0), borderwidth=0) + style.configure("TNotebook.Tab", background=self.col_field, + foreground=self.col_muted, bordercolor=self.col_line, + padding=(20, 9), font=(SCHRIFT_NAME, 10), borderwidth=0) style.map("TNotebook.Tab", - background=[("selected", bg), ("active", sel)], + background=[("selected", self.col_card), ("active", sel)], foreground=[("selected", accent), ("active", fg)], - font=[("selected", ("Segoe UI", 10, "bold"))], - expand=[("selected", (1, 1, 1, 0))]) + font=[("selected", (SCHRIFT_NAME, 10, "bold"))]) + # Kein expand beim ausgewaehlten Reiter: er wuchs dadurch um ein paar + # Pixel, die anderen rueckten nach - beim Umschalten sprang die ganze + # Leiste. Erkannt wird der offene Reiter an Farbe und Fettschrift. # Klassische tk-Widgets (Canvas, Toplevel, Text) erben diese Defaults self.option_add("*background", bg) self.option_add("*foreground", fg)