Oberflaeche im Pcore-Stil: Tuerkis, Kacheln, Glas-Knoepfe, Roboto

Seine Ansage: "schau dir unsern fivem server an und unsere webseiten, dann
weisst du wie mein design ist" - also die Palette aus _ui_www und der
Adminpage: Tuerkis #06C6A4 auf gestuften dunklen Flaechen (#0d0e10 Grund,
#15171b Kachel), Raender #25282e, Text #e8e8ea / #8a8e96, Rot #e85a5a.

Das PROGRAMM traegt diese Handschrift, die RECHNUNG die Farbe der jeweiligen
Firma - das Blatt gehoert dem Kunden, nicht uns.

- Roboto wird mitgeliefert und nur fuer den Prozess geladen
  (AddFontResourceEx mit FR_PRIVATE): keine Installation, keine Adminrechte,
  nach dem Schliessen ist der Rechner wie vorher. Fehlt sie, bleibt es bei
  Segoe UI. Die Schriftdateien gehen mit in die exe (spec).
- Abschnitte sind Kacheln mit Ueberschrift statt grauer Rahmen.
- Knoepfe im Glas-Stil wie in seinen Oberflaechen: getoente Flaeche, Rand in
  Akzent-Transparenz. Tk kann kein backdrop-filter - die Toenung wird
  ausgerechnet (theme.mischen), das Ergebnis ist auf einfarbigem Grund
  identisch. "PDF erstellen" fuehrt, "Storno" ist rot.
- Zebrastreifen in der Leistungsliste: bei 17 Zeilen verrutscht man sonst.
- Kein expand am offenen Reiter mehr - dadurch sprang beim Umschalten die
  ganze Leiste ("das taben macht den aktiven kleiner").
- Das Fenster fuer den gemeinsamen Ordner stand als einziges noch in Weiss da:
  es kam nie durch _style_dialog.

Dazu ein Fund, der nichts mit dem Design zu tun hat: die Pruefstaende erbten
"nummer_eingerichtet" aus der config DIESES Rechners. Sobald hier jemand die
Nummer einmal einrichtet, liefen sie in einen anderen Zweig und wurden rot -
ohne dass sich am Programm etwas geaendert haette. Sie legen den Zustand jetzt
selbst fest. Alle 14 gruen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V4t48uxDok5rJXhC9bX1Ax
This commit is contained in:
TheMockTv 2026-09-05 18:52:42 +02:00
parent 0ad03fd1a4
commit 5ebb02b267
17 changed files with 330 additions and 87 deletions

110
app.py
View file

@ -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("<KeyRelease>", 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 "

Binary file not shown.

BIN
assets/fonts/Roboto.ttf Normal file

Binary file not shown.

View file

@ -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)

View file

@ -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):

View file

@ -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()

View file

@ -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)

View file

@ -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)

View file

@ -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.

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

216
theme.py
View file

@ -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)