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:
parent
0ad03fd1a4
commit
5ebb02b267
17 changed files with 330 additions and 87 deletions
110
app.py
110
app.py
|
|
@ -39,6 +39,7 @@ from bestand import (hoechster_zaehler_im_ordner, naechste_freie_nummer,
|
||||||
vorhandene_rechnungen)
|
vorhandene_rechnungen)
|
||||||
from einzelinstanz import (eigene_pid_merken, einzelinstanz_pruefen, fenster_nach_vorn,
|
from einzelinstanz import (eigene_pid_merken, einzelinstanz_pruefen, fenster_nach_vorn,
|
||||||
laufende_pid_lesen)
|
laufende_pid_lesen)
|
||||||
|
import theme
|
||||||
from theme import ThemeMixin
|
from theme import ThemeMixin
|
||||||
from korrektur import KorrekturMixin
|
from korrektur import KorrekturMixin
|
||||||
from einstellungen import EinstellungenMixin
|
from einstellungen import EinstellungenMixin
|
||||||
|
|
@ -202,12 +203,13 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk):
|
||||||
|
|
||||||
# ---------------------------------------------------------------- Kopf
|
# ---------------------------------------------------------------- Kopf
|
||||||
def _build_kopf(self):
|
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")
|
top.pack(fill="x")
|
||||||
|
|
||||||
# Kunde (links)
|
# Kunde (links)
|
||||||
kunde = ttk.LabelFrame(top, text="Rechnung an (Kunde)", padding=8)
|
kunde = ttk.LabelFrame(top, text="RECHNUNG AN (KUNDE)", padding=14,
|
||||||
kunde.grid(row=0, column=0, sticky="nsew", padx=(0, 10))
|
style="Karte.TLabelframe")
|
||||||
|
kunde.grid(row=0, column=0, sticky="nsew", padx=(0, 14))
|
||||||
self.k_anrede = tk.StringVar()
|
self.k_anrede = tk.StringVar()
|
||||||
self.k_vorname = tk.StringVar()
|
self.k_vorname = tk.StringVar()
|
||||||
self.k_name = tk.StringVar() # Nachname oder Firma – die Zuordnung im Steuerjournal
|
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),
|
("PLZ / Ort *", self.k_plzort),
|
||||||
]
|
]
|
||||||
for i, (lbl, var) in enumerate(felder):
|
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:
|
if var is self.k_anrede:
|
||||||
# Auswahlliste, aber nicht gesperrt - Sonderfaelle kann man tippen.
|
# Auswahlliste, aber nicht gesperrt - Sonderfaelle kann man tippen.
|
||||||
w = ttk.Combobox(kunde, textvariable=var, width=36, values=ANREDEN)
|
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)
|
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 "
|
ttk.Label(kunde, text="* Pflicht – Name und Anschrift des Kunden gehören "
|
||||||
"auf die Rechnung (§ 14 Abs. 4 Nr. 1 UStG).",
|
"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))
|
row=len(felder), column=0, columnspan=2, sticky="w", pady=(6, 0))
|
||||||
kunde.columnconfigure(1, weight=1)
|
kunde.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
# Rechnungsinfos (rechts)
|
# 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")
|
info.grid(row=0, column=1, sticky="nsew")
|
||||||
self.r_nummer = tk.StringVar()
|
self.r_nummer = tk.StringVar()
|
||||||
self.r_datum = tk.StringVar(value=datetime.date.today().strftime("%d.%m.%Y"))
|
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)
|
date_vars = (self.r_datum, self.r_anreise, self.r_abreise)
|
||||||
self.datum_labels = {}
|
self.datum_labels = {}
|
||||||
for i, (lbl, var) in enumerate(rows):
|
for i, (lbl, var) in enumerate(rows):
|
||||||
beschriftung = ttk.Label(info, text=lbl)
|
beschriftung = ttk.Label(info, text=lbl, style="Karte.TLabel")
|
||||||
beschriftung.grid(row=i, column=0, sticky="w", pady=2)
|
beschriftung.grid(row=i, column=0, sticky="w", pady=4)
|
||||||
if var is self.r_anreise:
|
if var is self.r_anreise:
|
||||||
self.datum_labels["von"] = beschriftung
|
self.datum_labels["von"] = beschriftung
|
||||||
elif var is self.r_abreise:
|
elif var is self.r_abreise:
|
||||||
|
|
@ -267,14 +271,16 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk):
|
||||||
self.nummer_entry = ent
|
self.nummer_entry = ent
|
||||||
if var in date_vars: # Datum: Punkte automatisch, Cursor ans Ende
|
if var in date_vars: # Datum: Punkte automatisch, Cursor ans Ende
|
||||||
ent.bind("<KeyRelease>", self._auto_datum_evt)
|
ent.bind("<KeyRelease>", self._auto_datum_evt)
|
||||||
self.naechte_label = ttk.Label(info, text="Nächte")
|
self.naechte_label = ttk.Label(info, text="Nächte", style="Karte.TLabel")
|
||||||
self.naechte_label.grid(row=4, column=0, sticky="w", pady=2)
|
self.naechte_label.grid(row=4, column=0, sticky="w", pady=4)
|
||||||
ttk.Label(info, textvariable=self.r_naechte, font=("Segoe UI", 10, "bold")).grid(
|
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))
|
row=4, column=1, sticky="w", padx=(8, 0))
|
||||||
# Storno-Hinweis: steht nur da, solange ein Storno im Zwischenspeicher liegt.
|
# Storno-Hinweis: steht nur da, solange ein Storno im Zwischenspeicher liegt.
|
||||||
self.storno_hinweis = tk.StringVar(value="")
|
self.storno_hinweis = tk.StringVar(value="")
|
||||||
ttk.Label(info, textvariable=self.storno_hinweis, foreground="#b00020",
|
ttk.Label(info, textvariable=self.storno_hinweis, style="Karte.TLabel",
|
||||||
font=("Segoe UI", 9, "bold"), wraplength=200, justify="left").grid(
|
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))
|
row=5, column=0, columnspan=2, sticky="w", pady=(6, 0))
|
||||||
info.columnconfigure(1, weight=1)
|
info.columnconfigure(1, weight=1)
|
||||||
self.r_anreise.trace_add("write", lambda *a: self._update_naechte())
|
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.blaetter.add(montage_tab, text=" Montage ")
|
||||||
self._build_montage(montage_tab)
|
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)
|
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)
|
sb = ttk.Scrollbar(wrap, orient="vertical", command=canvas.yview)
|
||||||
self.pos_frame = ttk.Frame(canvas)
|
self.pos_frame = ttk.Frame(canvas)
|
||||||
self._pos_win = canvas.create_window((0, 0), window=self.pos_frame, anchor="nw")
|
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)
|
# Kopfzeile (gleiches Grid wie die Daten)
|
||||||
for i, (title, anchor, _) in enumerate(self.COLS):
|
for i, (title, anchor, _) in enumerate(self.COLS):
|
||||||
ttk.Label(self.pos_frame, text=title, anchor=anchor,
|
ttk.Label(self.pos_frame, text=title.upper(), anchor=anchor,
|
||||||
font=("Segoe UI", 9, "bold")).grid(row=0, column=i, padx=3, pady=(0, 3), sticky="ew")
|
style="Abschnitt.TLabel").grid(
|
||||||
|
row=0, column=i, padx=3, pady=(0, 5), sticky="ew")
|
||||||
ttk.Separator(self.pos_frame, orient="horizontal").grid(
|
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"]):
|
for idx, pos in enumerate(self.cfg["katalog"]):
|
||||||
r = idx + 2
|
r = idx + 2
|
||||||
|
|
@ -414,17 +422,26 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk):
|
||||||
av.trace_add("write", lambda *a: self.recalc())
|
av.trace_add("write", lambda *a: self.recalc())
|
||||||
pv.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,
|
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")
|
justify="left", style=zeilenstil).grid(
|
||||||
ttk.Label(self.pos_frame, text=str(pos["satz"]), anchor="center").grid(row=r, column=2, padx=3, pady=2, sticky="ew")
|
row=r, column=1, 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=3, pady=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
|
# Personen-Feld NUR bei "pro Person"-Positionen anzeigen
|
||||||
if pos["pro_person"]:
|
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.Entry(self.pos_frame, textvariable=pv, width=6, justify="center").grid(
|
||||||
ttk.Label(self.pos_frame, text=berechnung.eur_kurz(pos["preis"]), anchor="e").grid(row=r, column=5, padx=3, pady=2, sticky="ew")
|
row=r, column=4, padx=4, pady=3)
|
||||||
g = ttk.Label(self.pos_frame, text="0,00", anchor="e")
|
ttk.Label(self.pos_frame, text=berechnung.eur_kurz(pos["preis"]), anchor="e",
|
||||||
g.grid(row=r, column=6, padx=3, pady=2, sticky="ew")
|
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
|
self.gesamt_lbls[nr] = g
|
||||||
|
|
||||||
# ------------------------------------------------------------ Montage
|
# ------------------------------------------------------------ Montage
|
||||||
|
|
@ -440,7 +457,7 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk):
|
||||||
self.m_monate.trace_add("write", lambda *a: self.recalc())
|
self.m_monate.trace_add("write", lambda *a: self.recalc())
|
||||||
|
|
||||||
ttk.Label(eltern, text="Monatsmiete für einen Montagearbeiter",
|
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,
|
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). "
|
text="Den Zeitraum trägst du oben rechts ein (dieselben Felder wie Anreise/Abreise). "
|
||||||
"Bezeichnung, Steuersatz und Monatspreis stehen unter "
|
"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(
|
ttk.Entry(eltern, textvariable=self.m_monate, width=8, justify="center").grid(
|
||||||
row=2, column=1, sticky="w", padx=(8, 24), pady=3)
|
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)
|
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)
|
self.m_preis.grid(row=2, column=3, sticky="w", padx=(8, 0), pady=3)
|
||||||
|
|
||||||
ttk.Separator(eltern, orient="horizontal").grid(
|
ttk.Separator(eltern, orient="horizontal").grid(
|
||||||
row=3, column=0, columnspan=4, sticky="ew", pady=12)
|
row=3, column=0, columnspan=4, sticky="ew", pady=12)
|
||||||
|
|
||||||
ttk.Label(eltern, text="So steht es auf der Rechnung:",
|
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 = 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_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")
|
self.m_zahlen.grid(row=6, column=0, columnspan=4, sticky="w")
|
||||||
eltern.columnconfigure(3, weight=1)
|
eltern.columnconfigure(3, weight=1)
|
||||||
|
|
||||||
|
|
@ -522,14 +539,14 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk):
|
||||||
status = ttk.Frame(self, padding=(12, 2))
|
status = ttk.Frame(self, padding=(12, 2))
|
||||||
status.pack(side="bottom", fill="x")
|
status.pack(side="bottom", fill="x")
|
||||||
ttk.Label(status, text="powered by ravokk", foreground=self.col_muted,
|
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
|
# Steht der gemeinsame Ordner? Das gehoert sichtbar hin - ohne ihn kennt
|
||||||
# das Programm die Nummern des Steuerjournals nicht.
|
# das Programm die Nummern des Steuerjournals nicht.
|
||||||
self.buch_lbl = ttk.Label(status, text="", foreground=self.col_muted,
|
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")
|
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")
|
f.pack(side="bottom", fill="x")
|
||||||
ttk.Separator(self, orient="horizontal").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 = ttk.Frame(f)
|
||||||
links.pack(side="left")
|
links.pack(side="left")
|
||||||
ttk.Button(links, text="Vorschau", command=self.vorschau_zeigen, width=12).pack(side="left", padx=4)
|
# "PDF erstellen" ist der Knopf, um den es geht - der traegt die
|
||||||
ttk.Button(links, text="PDF erstellen", command=self.pdf_erstellen, width=18).pack(side="left", padx=4)
|
# 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,
|
ttk.Button(links, text="Storno", command=self.dlg_storno,
|
||||||
width=12).pack(side="left", padx=4)
|
style="Gefahr.TButton").pack(side="left", padx=6)
|
||||||
ttk.Button(links, text="Berichtigen", command=self.dlg_berichtigen,
|
ttk.Button(links, text="Berichtigen", command=self.dlg_berichtigen).pack(side="left", padx=6)
|
||||||
width=14).pack(side="left", padx=4)
|
ttk.Button(links, text="Zurücksetzen", command=self.reset_formular).pack(side="left", padx=6)
|
||||||
ttk.Button(links, text="Zurücksetzen", command=self.reset_formular, width=14).pack(side="left", padx=4)
|
|
||||||
|
|
||||||
# --------------------------------------------------- Gemeinsames Buch
|
# --------------------------------------------------- Gemeinsames Buch
|
||||||
def _buch_oeffnen(self):
|
def _buch_oeffnen(self):
|
||||||
|
|
@ -638,7 +659,7 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk):
|
||||||
rahmen = ttk.Frame(d, padding=18)
|
rahmen = ttk.Frame(d, padding=18)
|
||||||
rahmen.pack(fill="both", expand=True)
|
rahmen.pack(fill="both", expand=True)
|
||||||
ttk.Label(rahmen, text="Wo soll die Rechnungsnummer weiterlaufen?",
|
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=(
|
ttk.Label(rahmen, wraplength=620, justify="left", text=(
|
||||||
"Zwei Wege - such dir einen aus." + chr(10) + chr(10) +
|
"Zwei Wege - such dir einen aus." + chr(10) + chr(10) +
|
||||||
"Das wird EINMAL eingerichtet. Danach steht der Zähler fest: eine "
|
"Das wird EINMAL eingerichtet. Danach steht der Zähler fest: eine "
|
||||||
|
|
@ -649,7 +670,7 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk):
|
||||||
|
|
||||||
if gefunden:
|
if gefunden:
|
||||||
tk.Label(rahmen, wraplength=620, justify="left", fg="#1f5c3d",
|
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 "
|
text=f"Im Rechnungsordner und im Nummernbuch steht schon "
|
||||||
f"{jahr}-{gefunden:03d} – niedriger geht es nicht.").grid(
|
f"{jahr}-{gefunden:03d} – niedriger geht es nicht.").grid(
|
||||||
row=2, column=0, columnspan=2, sticky="w", pady=(0, 10))
|
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))
|
eingabe.pack(side="left", padx=(8, 0))
|
||||||
weg_gewaehlt()
|
weg_gewaehlt()
|
||||||
hinweis = tk.Label(rahmen, text="", wraplength=620, justify="left", fg="#a00000",
|
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))
|
hinweis.grid(row=5, column=0, columnspan=2, sticky="w", pady=(8, 0))
|
||||||
|
|
||||||
def uebernehmen():
|
def uebernehmen():
|
||||||
|
|
@ -957,7 +978,8 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk):
|
||||||
rows.append(("Gesamtbetrag:", berechnung.eur(r.gesamt), True))
|
rows.append(("Gesamtbetrag:", berechnung.eur(r.gesamt), True))
|
||||||
|
|
||||||
for i, (lbl, val, fett) in enumerate(rows):
|
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)
|
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)
|
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"}
|
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 = ttk.Frame(d, padding=18)
|
||||||
rahmen.pack(fill="both", expand=True)
|
rahmen.pack(fill="both", expand=True)
|
||||||
ttk.Label(rahmen, text=f"{wer or 'ohne Namen'} – {betrag}",
|
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=(
|
ttk.Label(rahmen, wraplength=560, justify="left", text=(
|
||||||
f"RECHNUNG {nummer} – der Beleg. Danach lässt sie sich nicht mehr "
|
f"RECHNUNG {nummer} – der Beleg. Danach lässt sie sich nicht mehr "
|
||||||
|
|
|
||||||
BIN
assets/fonts/Roboto-Italic.ttf
Normal file
BIN
assets/fonts/Roboto-Italic.ttf
Normal file
Binary file not shown.
BIN
assets/fonts/Roboto.ttf
Normal file
BIN
assets/fonts/Roboto.ttf
Normal file
Binary file not shown.
|
|
@ -11,6 +11,7 @@ import tkinter as tk
|
||||||
from tkinter import ttk, messagebox, filedialog, colorchooser
|
from tkinter import ttk, messagebox, filedialog, colorchooser
|
||||||
|
|
||||||
import berechnung
|
import berechnung
|
||||||
|
import theme
|
||||||
import bilder
|
import bilder
|
||||||
import config
|
import config
|
||||||
from helfer import parse_num
|
from helfer import parse_num
|
||||||
|
|
@ -50,7 +51,7 @@ class EinstellungenMixin:
|
||||||
self._style_dialog(d)
|
self._style_dialog(d)
|
||||||
frm = ttk.Frame(d, padding=18)
|
frm = ttk.Frame(d, padding=18)
|
||||||
frm.pack(fill="both", expand=True)
|
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")
|
foreground=self.col_accent).pack(anchor="w")
|
||||||
ttk.Label(frm, justify="left",
|
ttk.Label(frm, justify="left",
|
||||||
text="Daten eingeben, PDF erstellen, drucken.\n"
|
text="Daten eingeben, PDF erstellen, drucken.\n"
|
||||||
|
|
@ -74,7 +75,7 @@ class EinstellungenMixin:
|
||||||
frm = ttk.Frame(d, padding=12)
|
frm = ttk.Frame(d, padding=12)
|
||||||
frm.pack(fill="both", expand=True)
|
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 "
|
text="Steuersätze: Satz % + Name. Die Katalog-Preise sind BRUTTO; die USt dieses "
|
||||||
"Satzes wird herausgerechnet und als 'enthalten' ausgewiesen. Haken bei "
|
"Satzes wird herausgerechnet und als 'enthalten' ausgewiesen. Haken bei "
|
||||||
"'Aufschlag': diese Steuer (z.B. Beherbergungssteuer) wird stattdessen auf den "
|
"'Aufschlag': diese Steuer (z.B. Beherbergungssteuer) wird stattdessen auf den "
|
||||||
|
|
@ -92,7 +93,7 @@ class EinstellungenMixin:
|
||||||
for w in liste.winfo_children():
|
for w in liste.winfo_children():
|
||||||
w.destroy()
|
w.destroy()
|
||||||
for c, t in enumerate(["Satz %", "Bezeichnung (Name der Steuer)", "Aufschlag", ""]):
|
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")
|
row=0, column=c, padx=4, pady=(0, 3), sticky="w")
|
||||||
for i, row in enumerate(arbeit, start=1):
|
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)
|
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():
|
for w in inner.winfo_children():
|
||||||
w.destroy()
|
w.destroy()
|
||||||
for c, t in enumerate(["Pos.", "Bezeichnung", "Steuersatz", "Preis €", "pro Person", ""]):
|
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")
|
row=0, column=c, padx=4, pady=(0, 4), sticky="w")
|
||||||
for i, row in enumerate(arbeit, start=1):
|
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)
|
ttk.Label(inner, text=str(i), width=4).grid(row=i, column=0, padx=4, pady=1)
|
||||||
|
|
|
||||||
11
korrektur.py
11
korrektur.py
|
|
@ -23,6 +23,7 @@ import tkinter as tk
|
||||||
from tkinter import ttk, messagebox
|
from tkinter import ttk, messagebox
|
||||||
|
|
||||||
import berechnung
|
import berechnung
|
||||||
|
import theme
|
||||||
import config
|
import config
|
||||||
import pdf_renderer
|
import pdf_renderer
|
||||||
import berechnung
|
import berechnung
|
||||||
|
|
@ -375,7 +376,7 @@ class KorrekturMixin:
|
||||||
rahmen = ttk.Frame(d, padding=18)
|
rahmen = ttk.Frame(d, padding=18)
|
||||||
rahmen.pack(fill="both", expand=True)
|
rahmen.pack(fill="both", expand=True)
|
||||||
ttk.Label(rahmen, text=f"Storno zu Rechnung {alt_nummer}",
|
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 ------------------------------------------
|
# ---- Schritt 1: A oder B ------------------------------------------
|
||||||
schritt1 = ttk.Frame(rahmen)
|
schritt1 = ttk.Frame(rahmen)
|
||||||
|
|
@ -384,17 +385,17 @@ class KorrekturMixin:
|
||||||
"Auf dem Storno-Blatt muss stehen, wie die Leistung stattdessen abgerechnet "
|
"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))
|
"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",
|
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=(
|
ttk.Label(schritt1, wraplength=620, justify="left", text=(
|
||||||
"Der übliche Weg: die Rechnung war falsch und wird nach dem Storno richtig neu "
|
"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))
|
"geschrieben. Das Formular bleibt danach offen.")).pack(anchor="w", pady=(2, 10))
|
||||||
ttk.Label(schritt1, text="B – die Leistung ist schon abgerechnet",
|
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=(
|
ttk.Label(schritt1, wraplength=620, justify="left", text=(
|
||||||
"Für Doppelte: derselbe Aufenthalt steht mehrfach im Ordner. Im nächsten Schritt "
|
"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))
|
"trägst du die Nummer der Rechnung ein, die bleibt.")).pack(anchor="w", pady=(2, 10))
|
||||||
ttk.Label(schritt1, text="C – nur Storno",
|
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=(
|
ttk.Label(schritt1, wraplength=620, justify="left", text=(
|
||||||
"Die Leistung wird gar nicht abgerechnet: Buchung geplatzt oder die Rechnung "
|
"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 "
|
"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 = ttk.Entry(zeile, textvariable=eingabe, width=16, justify="center")
|
||||||
feld.pack(side="left", padx=(8, 0))
|
feld.pack(side="left", padx=(8, 0))
|
||||||
stand = tk.Label(schritt2, text="", wraplength=620, justify="left",
|
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))
|
stand.pack(anchor="w", pady=(8, 0))
|
||||||
|
|
||||||
def suchen(*_a):
|
def suchen(*_a):
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ import os
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk, filedialog, messagebox
|
from tkinter import ttk, filedialog, messagebox
|
||||||
|
|
||||||
|
import theme
|
||||||
|
|
||||||
import gemeinsam
|
import gemeinsam
|
||||||
|
|
||||||
BREITE = 780
|
BREITE = 780
|
||||||
|
|
@ -39,6 +41,18 @@ class OrdnerWahl(tk.Toplevel):
|
||||||
self.ergebnis = None
|
self.ergebnis = None
|
||||||
self.title("Gemeinsamer Ordner für das Nummernbuch")
|
self.title("Gemeinsamer Ordner für das Nummernbuch")
|
||||||
self.transient(master)
|
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 "")
|
self.pfad = tk.StringVar(value=vorgabe or "")
|
||||||
|
|
||||||
rahmen = ttk.Frame(self, padding=18)
|
rahmen = ttk.Frame(self, padding=18)
|
||||||
|
|
@ -47,7 +61,7 @@ class OrdnerWahl(tk.Toplevel):
|
||||||
breite = BREITE - 60
|
breite = BREITE - 60
|
||||||
|
|
||||||
ttk.Label(rahmen, text="Bitte den gemeinsamen Ordner auswählen",
|
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=(
|
ttk.Label(rahmen, wraplength=breite, justify="left", text=(
|
||||||
"Rechnungstool und Steuerjournal führen zusammen ein Nummernbuch. Darin steht "
|
"Rechnungstool und Steuerjournal führen zusammen ein Nummernbuch. Darin steht "
|
||||||
"jede vergebene Rechnungsnummer – auch die aus der alten Excel-Tabelle, zu der "
|
"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 "
|
"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))
|
"(§ 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",
|
tk.Label(rahmen, wraplength=breite, justify="left", fg=self._warn, bg=self._bg,
|
||||||
font=("Segoe UI", 10, "bold"),
|
font=(theme.SCHRIFT_NAME, 10, "bold"),
|
||||||
text="WICHTIG: In BEIDEN Programmen muss derselbe Ordner stehen – am besten "
|
text="WICHTIG: In BEIDEN Programmen muss derselbe Ordner stehen – am besten "
|
||||||
"der Ordner, in dem die Programme liegen.").grid(
|
"der Ordner, in dem die Programme liegen.").grid(
|
||||||
row=2, column=0, sticky="w", pady=(0, 14))
|
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))
|
width=26).grid(row=0, column=1, sticky="w", padx=(10, 0))
|
||||||
|
|
||||||
self.stand = tk.Label(rahmen, text="", wraplength=breite, justify="left",
|
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))
|
self.stand.grid(row=4, column=0, sticky="w", pady=(10, 0))
|
||||||
|
|
||||||
knoepfe = ttk.Frame(rahmen)
|
knoepfe = ttk.Frame(rahmen)
|
||||||
|
|
@ -127,12 +142,12 @@ class OrdnerWahl(tk.Toplevel):
|
||||||
"""
|
"""
|
||||||
ordner = self.pfad.get().strip()
|
ordner = self.pfad.get().strip()
|
||||||
if not ordner:
|
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"])
|
self.ok.state(["disabled"])
|
||||||
return
|
return
|
||||||
taugt, grund = gemeinsam.ordner_taugt(ordner)
|
taugt, grund = gemeinsam.ordner_taugt(ordner)
|
||||||
if not taugt:
|
if not taugt:
|
||||||
self.stand.configure(text=grund, fg="#a00000")
|
self.stand.configure(text=grund, fg=self._warn)
|
||||||
self.ok.state(["disabled"])
|
self.ok.state(["disabled"])
|
||||||
return
|
return
|
||||||
self.ok.state(["!disabled"])
|
self.ok.state(["!disabled"])
|
||||||
|
|
@ -144,11 +159,11 @@ class OrdnerWahl(tk.Toplevel):
|
||||||
self.stand.configure(
|
self.stand.configure(
|
||||||
text=f"Hier liegt schon ein Nummernbuch mit {anzahl} Rechnungsnummern – "
|
text=f"Hier liegt schon ein Nummernbuch mit {anzahl} Rechnungsnummern – "
|
||||||
"das ist der Ordner des anderen Programms. Genau richtig.",
|
"das ist der Ordner des anderen Programms. Genau richtig.",
|
||||||
fg="#1f5c3d")
|
fg=self._gut)
|
||||||
else:
|
else:
|
||||||
self.stand.configure(
|
self.stand.configure(
|
||||||
text="Hier ist noch kein Nummernbuch – es wird neu angelegt. Wähle im "
|
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):
|
def uebernehmen(self):
|
||||||
ordner = self.pfad.get().strip()
|
ordner = self.pfad.get().strip()
|
||||||
|
|
|
||||||
|
|
@ -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")
|
config.CONFIG_PATH = os.path.join(tmp, "config.json")
|
||||||
roh = json.load(open(config.CONFIG_PATH, encoding="utf-8"))
|
roh = json.load(open(config.CONFIG_PATH, encoding="utf-8"))
|
||||||
roh["rechnung_jahr"], roh["rechnung_zaehler"] = 2026, 0
|
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["output_dir"] = ordner
|
||||||
json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False, indent=2)
|
json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,10 @@ config.BASE_DIR = tmp
|
||||||
config.CONFIG_PATH = 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 = json.load(open(config.CONFIG_PATH, encoding="utf-8"))
|
||||||
roh["rechnung_jahr"], roh["rechnung_zaehler"] = 2026, 0
|
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["output_dir"] = ordner
|
||||||
roh["gemeinsam_ordner"] = gemeinsam_ordner
|
roh["gemeinsam_ordner"] = gemeinsam_ordner
|
||||||
json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False, indent=2)
|
json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False, indent=2)
|
||||||
|
|
|
||||||
|
|
@ -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")
|
config.CONFIG_PATH = os.path.join(tmp, "config.json")
|
||||||
roh = json.load(open(config.CONFIG_PATH, encoding="utf-8"))
|
roh = json.load(open(config.CONFIG_PATH, encoding="utf-8"))
|
||||||
roh["output_dir"] = ordner
|
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)
|
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.
|
# Kein Erststart-Dialog im Pruefstand - er wuerde den Lauf haengen lassen.
|
||||||
|
|
|
||||||
|
|
@ -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")
|
config.CONFIG_PATH = os.path.join(tmp, "config.json")
|
||||||
roh = json.load(open(config.CONFIG_PATH, encoding="utf-8"))
|
roh = json.load(open(config.CONFIG_PATH, encoding="utf-8"))
|
||||||
roh["rechnung_jahr"], roh["rechnung_zaehler"] = 2026, 0
|
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["output_dir"] = ordner
|
||||||
json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False)
|
json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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")
|
config.CONFIG_PATH = os.path.join(tmp, "config.json")
|
||||||
roh = json.load(open(config.CONFIG_PATH, encoding="utf-8"))
|
roh = json.load(open(config.CONFIG_PATH, encoding="utf-8"))
|
||||||
roh["rechnung_jahr"], roh["rechnung_zaehler"] = 2026, 0
|
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["output_dir"] = ordner
|
||||||
json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False)
|
json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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")
|
config.CONFIG_PATH = os.path.join(tmp, "config.json")
|
||||||
roh = json.load(open(config.CONFIG_PATH, encoding="utf-8"))
|
roh = json.load(open(config.CONFIG_PATH, encoding="utf-8"))
|
||||||
roh["rechnung_jahr"], roh["rechnung_zaehler"] = 2026, 0
|
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["output_dir"] = ordner
|
||||||
json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False, indent=2)
|
json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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")
|
config.CONFIG_PATH = os.path.join(tmp, "config.json")
|
||||||
roh = json.load(open(config.CONFIG_PATH, encoding="utf-8"))
|
roh = json.load(open(config.CONFIG_PATH, encoding="utf-8"))
|
||||||
roh["rechnung_jahr"], roh["rechnung_zaehler"] = 2026, 0
|
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["output_dir"] = rechnungen
|
||||||
roh["gemeinsam_ordner"] = gemeinsam_ordner
|
roh["gemeinsam_ordner"] = gemeinsam_ordner
|
||||||
json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False, indent=2)
|
json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False, indent=2)
|
||||||
|
|
|
||||||
|
|
@ -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")
|
config.CONFIG_PATH = os.path.join(tmp, "config.json")
|
||||||
roh = json.load(open(config.CONFIG_PATH, encoding="utf-8"))
|
roh = json.load(open(config.CONFIG_PATH, encoding="utf-8"))
|
||||||
roh["rechnung_jahr"], roh["rechnung_zaehler"] = 2026, 0
|
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
|
roh["output_dir"] = ordner
|
||||||
json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False, indent=2)
|
json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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")
|
config.CONFIG_PATH = os.path.join(tmp, "config.json")
|
||||||
roh = json.load(open(config.CONFIG_PATH, encoding="utf-8"))
|
roh = json.load(open(config.CONFIG_PATH, encoding="utf-8"))
|
||||||
roh["rechnung_jahr"], roh["rechnung_zaehler"] = 2026, 0
|
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["output_dir"] = ordner
|
||||||
json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False, indent=2)
|
json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,10 @@ config.BASE_DIR = tmp
|
||||||
config.CONFIG_PATH = 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 = json.load(open(config.CONFIG_PATH, encoding="utf-8"))
|
||||||
roh["rechnung_jahr"], roh["rechnung_zaehler"] = 2026, 0
|
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["output_dir"] = ordner
|
||||||
json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False)
|
json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False)
|
||||||
|
|
||||||
|
|
|
||||||
216
theme.py
216
theme.py
|
|
@ -7,11 +7,85 @@ baut das Programm Menueleiste und Dropdowns selbst; hier stehen nur die Farben.
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
|
from tkinter import font as tkfont
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
|
||||||
import config
|
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():
|
def windows_dark_mode():
|
||||||
"""True, wenn Windows auf das dunkle App-Theme eingestellt ist."""
|
"""True, wenn Windows auf das dunkle App-Theme eingestellt ist."""
|
||||||
if not sys.platform.startswith("win"):
|
if not sys.platform.startswith("win"):
|
||||||
|
|
@ -32,26 +106,42 @@ class ThemeMixin:
|
||||||
|
|
||||||
def _apply_theme(self):
|
def _apply_theme(self):
|
||||||
"""Farben passend zum Windows-Theme (hell/dunkel) setzen."""
|
"""Farben passend zum Windows-Theme (hell/dunkel) setzen."""
|
||||||
|
schrift_vorbereiten() # Roboto, sonst Segoe UI
|
||||||
dark = windows_dark_mode()
|
dark = windows_dark_mode()
|
||||||
self._dark_mode = dark
|
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:
|
if dark:
|
||||||
self.col_bg = "#23272e" # Fenster-Hintergrund
|
self.col_bg = "#0d0e10" # Fenster-Hintergrund (tiefstes Grau)
|
||||||
self.col_fg = "#e6e6e6" # Schrift
|
self.col_card = "#15171b" # Kachel / Abschnitt
|
||||||
self.col_field = "#2f343c" # Eingabefelder
|
self.col_fg = "#e8e8ea" # Schrift
|
||||||
self.col_sel = "#3a4a44" # aktiv/hover
|
self.col_field = "#1d2025" # Eingabefelder
|
||||||
self.col_accent = "#5fb98f" # Akzent (Gruen, hell)
|
self.col_sel = "#1f3d38" # aktiv/hover (Akzent, stark abgedunkelt)
|
||||||
self.col_line = "#3a3f47"
|
self.col_accent = "#06C6A4" # Akzent (Pcore-Tuerkis)
|
||||||
self.col_scroll = "#4a505a" # Scroll-Griff
|
self.col_accent_hell = "#08dbb5"
|
||||||
self.col_muted = "#8a929c" # gedaempfte Schrift (z.B. "powered by")
|
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:
|
else:
|
||||||
self.col_bg = "#f3f3f3"
|
self.col_bg = "#eef0f3"
|
||||||
self.col_fg = "#1a1a1a"
|
self.col_card = "#ffffff"
|
||||||
|
self.col_fg = "#14161a"
|
||||||
self.col_field = "#ffffff"
|
self.col_field = "#ffffff"
|
||||||
self.col_sel = "#cfe3d8"
|
self.col_sel = "#d7f5ee"
|
||||||
self.col_accent = "#1f5c3d"
|
self.col_accent = "#04886f" # dunkler, damit es auf Weiss lesbar ist
|
||||||
self.col_line = "#cccccc"
|
self.col_accent_hell = "#06C6A4"
|
||||||
self.col_scroll = "#b8b8b8"
|
self.col_auf_accent = "#ffffff"
|
||||||
self.col_muted = "#777777" # gedaempfte Schrift (z.B. "powered by")
|
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)
|
self.configure(bg=self.col_bg)
|
||||||
style = ttk.Style(self)
|
style = ttk.Style(self)
|
||||||
|
|
@ -64,17 +154,89 @@ class ThemeMixin:
|
||||||
style.configure(".", background=bg, foreground=fg, fieldbackground=field,
|
style.configure(".", background=bg, foreground=fg, fieldbackground=field,
|
||||||
bordercolor=self.col_line, lightcolor=bg, darkcolor=bg)
|
bordercolor=self.col_line, lightcolor=bg, darkcolor=bg)
|
||||||
style.configure("TFrame", background=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", background=bg, bordercolor=self.col_line)
|
||||||
style.configure("TLabelframe.Label", background=bg, foreground=accent)
|
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",
|
style.configure("Menu.TMenubutton", background=bg, foreground=fg, relief="flat",
|
||||||
arrowcolor=bg, padding=(10, 3))
|
arrowcolor=bg, padding=(10, 3))
|
||||||
style.map("Menu.TMenubutton", background=[("active", sel)], foreground=[("active", fg)])
|
style.map("Menu.TMenubutton", background=[("active", sel)], foreground=[("active", fg)])
|
||||||
style.configure("TCheckbutton", background=bg, foreground=fg)
|
style.configure("TCheckbutton", background=bg, foreground=fg)
|
||||||
style.map("TCheckbutton", background=[("active", bg)], foreground=[("active", 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,
|
# Bei state="readonly" zeichnet die Combobox ihren Text als MARKIERUNG,
|
||||||
# sobald sie den Fokus hat - ohne diese beiden Farben steht der Wert
|
# sobald sie den Fokus hat - ohne diese beiden Farben steht der Wert
|
||||||
# unsichtbar im Feld.
|
# unsichtbar im Feld.
|
||||||
|
|
@ -101,15 +263,17 @@ class ThemeMixin:
|
||||||
# ist. Der offene Reiter bekommt darum den Hintergrund des Blattes und die
|
# ist. Der offene Reiter bekommt darum den Hintergrund des Blattes und die
|
||||||
# Akzentfarbe, die ruhenden bleiben zurueckgenommen.
|
# Akzentfarbe, die ruhenden bleiben zurueckgenommen.
|
||||||
style.configure("TNotebook", background=bg, bordercolor=self.col_line,
|
style.configure("TNotebook", background=bg, bordercolor=self.col_line,
|
||||||
tabmargins=(2, 4, 2, 0))
|
tabmargins=(2, 4, 2, 0), borderwidth=0)
|
||||||
style.configure("TNotebook.Tab", background=field, foreground=self.col_muted,
|
style.configure("TNotebook.Tab", background=self.col_field,
|
||||||
bordercolor=self.col_line, padding=(16, 7),
|
foreground=self.col_muted, bordercolor=self.col_line,
|
||||||
font=("Segoe UI", 10))
|
padding=(20, 9), font=(SCHRIFT_NAME, 10), borderwidth=0)
|
||||||
style.map("TNotebook.Tab",
|
style.map("TNotebook.Tab",
|
||||||
background=[("selected", bg), ("active", sel)],
|
background=[("selected", self.col_card), ("active", sel)],
|
||||||
foreground=[("selected", accent), ("active", fg)],
|
foreground=[("selected", accent), ("active", fg)],
|
||||||
font=[("selected", ("Segoe UI", 10, "bold"))],
|
font=[("selected", (SCHRIFT_NAME, 10, "bold"))])
|
||||||
expand=[("selected", (1, 1, 1, 0))])
|
# 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
|
# Klassische tk-Widgets (Canvas, Toplevel, Text) erben diese Defaults
|
||||||
self.option_add("*background", bg)
|
self.option_add("*background", bg)
|
||||||
self.option_add("*foreground", fg)
|
self.option_add("*foreground", fg)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue