Modernes Design (Standard, Win95 als Legacy), Zeitraum abrechnen, Abrechnung erledigt, Linux-Build
- Design: „modern“ (hell, flach, wie Windows 11, normaler Windows-Rahmen) ist Standard; „win95“ bleibt unter Datei > Einstellungen wählbar (Einstellung „design“). - Export: Zeitraum von–bis (nach Aktivierungsdatum oder Abrechnungszeitraum des Anbieters), exportierte Positionen werden als abgerechnet gekennzeichnet (Betrag/Händler eingefroren) und per Fingerabdruck auch in späteren Dateien/Abrechnungen nicht noch einmal abgerechnet. - Datei > Abrechnung erledigt / wieder öffnen: erledigte Abrechnungen sind gegen Änderungen gesperrt. - DB-Schema v4 (Migration inkl. Fingerabdrücke für alte Positionen). - Linux-Build (python:3.12-bookworm) als zweiter Pipeline-Job mit Tests und Selbsttest; Release-Upload für beide Jobs über build/release_hochladen.sh. - Behoben: normaler Windows-Rahmen stürzte beim Start ab (Hauptrahmen.nativ fehlte). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5a9d6d19a2
commit
da8a7138bd
17 files changed
+829
-80
No files matched your search
+115
-5
@@ -1,4 +1,8 @@
|
||||
"""Windows-95-Optik für Tkinter: Farben, Schriften, Pixel-Icons und ein paar Bausteine."""
|
||||
"""Optik für Tkinter: Farben, Schriften, Pixel-Icons und ein paar Bausteine.
|
||||
|
||||
Zwei Designs: „modern“ (hell und flach, wie Windows 11 – Standard) und „win95“ (klassisch, Legacy).
|
||||
design_setzen() muss VOR dem Aufbau der Oberfläche laufen; alle Module lesen die Farben als w.FACE usw.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
@@ -14,6 +18,19 @@ NAVY = "#000080"
|
||||
TEAL = "#008080"
|
||||
WHITE = "#ffffff"
|
||||
INFO_BG = "#ffffe1"
|
||||
SEL_BG, SEL_FG = NAVY, WHITE # Auswahl/Markierung
|
||||
RAND = SHADOW # dünne Rahmenlinien (nur modern)
|
||||
DESIGN = "win95"
|
||||
MODERN = False
|
||||
|
||||
_PALETTEN = {
|
||||
"win95": dict(FACE="#c0c0c0", HILITE="#ffffff", LIGHT="#dfdfdf", SHADOW="#808080", DARK="#000000",
|
||||
NAVY="#000080", TEAL="#008080", WHITE="#ffffff", INFO_BG="#ffffe1", SEL_BG="#000080",
|
||||
SEL_FG="#ffffff", RAND="#808080"),
|
||||
"modern": dict(FACE="#f3f3f3", HILITE="#ffffff", LIGHT="#ebebeb", SHADOW="#8a8a8a", DARK="#1b1b1b",
|
||||
NAVY="#005fb8", TEAL="#e6eef6", WHITE="#ffffff", INFO_BG="#ffffff", SEL_BG="#cce4f7",
|
||||
SEL_FG="#1b1b1b", RAND="#d1d1d1"),
|
||||
}
|
||||
|
||||
IS_WIN = sys.platform.startswith("win")
|
||||
FONT_FAMILY = "MS Sans Serif" if IS_WIN else "DejaVu Sans"
|
||||
@@ -23,6 +40,24 @@ FONT_BOLD = (FONT_FAMILY, FONT_SIZE, "bold")
|
||||
FONT_BIG = (FONT_FAMILY, FONT_SIZE + 4, "bold")
|
||||
FONT_MONO = ("Courier New" if IS_WIN else "DejaVu Sans Mono", FONT_SIZE + 1)
|
||||
|
||||
|
||||
def design_setzen(name: str) -> None:
|
||||
"""„modern“ oder „win95“ – setzt Farben und Schriften (Feinschliff der Schrift in apply())."""
|
||||
global DESIGN, MODERN, FONT_FAMILY, FONT_SIZE
|
||||
DESIGN = name if name in _PALETTEN else "modern"
|
||||
MODERN = DESIGN == "modern"
|
||||
globals().update(_PALETTEN[DESIGN])
|
||||
FONT_FAMILY = ("Segoe UI" if MODERN else "MS Sans Serif") if IS_WIN else "DejaVu Sans"
|
||||
FONT_SIZE = 9 if (MODERN or not IS_WIN) else 8
|
||||
_schriften()
|
||||
|
||||
|
||||
def _schriften():
|
||||
global FONT, FONT_BOLD, FONT_BIG
|
||||
FONT = (FONT_FAMILY, FONT_SIZE)
|
||||
FONT_BOLD = (FONT_FAMILY, FONT_SIZE, "bold")
|
||||
FONT_BIG = (FONT_FAMILY, FONT_SIZE + 4, "bold")
|
||||
|
||||
# --------------------------------------------------------------------------- Pixel-Icons (16x16)
|
||||
PALETTE = {
|
||||
"k": "#000000", "w": "#ffffff", "g": "#c0c0c0", "d": "#808080", "y": "#ffff00", "Y": "#808000",
|
||||
@@ -288,6 +323,13 @@ class Icons:
|
||||
# --------------------------------------------------------------------------- Theme
|
||||
|
||||
def apply(root: tk.Tk) -> None:
|
||||
global FONT_FAMILY
|
||||
if MODERN and not IS_WIN: # Linux: die erste vorhandene moderne Schrift
|
||||
from tkinter import font as tkfont
|
||||
vorhanden = set(tkfont.families(root))
|
||||
FONT_FAMILY = next((f for f in ("Segoe UI", "Noto Sans", "Cantarell", "Ubuntu", "DejaVu Sans")
|
||||
if f in vorhanden), FONT_FAMILY)
|
||||
_schriften()
|
||||
root.configure(bg=FACE)
|
||||
o = root.option_add
|
||||
o("*Font", FONT)
|
||||
@@ -343,6 +385,45 @@ def apply(root: tk.Tk) -> None:
|
||||
selectforeground=[("readonly", DARK)])
|
||||
st.configure("TPanedwindow", background=FACE)
|
||||
st.configure("Sash", sashthickness=6, background=FACE)
|
||||
if MODERN:
|
||||
_modern(root, st, zeile)
|
||||
|
||||
|
||||
def _modern(root: tk.Tk, st: ttk.Style, zeile: int) -> None:
|
||||
"""Flach und hell: dünne graue Rahmen statt 3D-Kanten, Akzentfarbe beim Fokus."""
|
||||
o = root.option_add
|
||||
for klasse in ("Entry", "Text", "Listbox", "Spinbox"):
|
||||
o(f"*{klasse}.relief", "flat")
|
||||
o(f"*{klasse}.borderWidth", 3)
|
||||
o(f"*{klasse}.highlightThickness", 1)
|
||||
o(f"*{klasse}.highlightBackground", RAND)
|
||||
o(f"*{klasse}.highlightColor", NAVY)
|
||||
o("*Button.relief", "flat")
|
||||
o("*Button.borderWidth", 0)
|
||||
o("*Button.highlightThickness", 1)
|
||||
o("*Button.highlightBackground", RAND)
|
||||
o("*Button.background", WHITE)
|
||||
o("*Button.activeBackground", SEL_BG)
|
||||
o("*Button.padY", 3)
|
||||
o("*Checkbutton.activeBackground", FACE)
|
||||
o("*Radiobutton.activeBackground", FACE)
|
||||
o("*Menu.relief", "flat")
|
||||
st.theme_use("clam")
|
||||
st.configure(".", background=FACE, foreground=DARK, font=FONT, troughcolor=FACE, selectbackground=SEL_BG,
|
||||
selectforeground=SEL_FG, fieldbackground=WHITE, bordercolor=RAND, lightcolor=WHITE,
|
||||
darkcolor=RAND, focuscolor=NAVY)
|
||||
st.configure("Treeview", background=WHITE, fieldbackground=WHITE, rowheight=zeile + 4, borderwidth=0, font=FONT)
|
||||
st.map("Treeview", background=[("selected", SEL_BG)], foreground=[("selected", SEL_FG)])
|
||||
st.configure("Treeview.Heading", background=WHITE, relief="flat", borderwidth=1, font=FONT, padding=(6, 3),
|
||||
bordercolor=RAND, lightcolor=WHITE, darkcolor=RAND)
|
||||
st.map("Treeview.Heading", background=[("active", LIGHT)], relief=[("pressed", "flat")])
|
||||
st.configure("TScrollbar", background=LIGHT, troughcolor=FACE, arrowcolor=SHADOW, bordercolor=FACE,
|
||||
lightcolor=LIGHT, darkcolor=LIGHT, relief="flat", arrowsize=12, width=12)
|
||||
st.map("TScrollbar", background=[("active", RAND)])
|
||||
st.configure("TCombobox", fieldbackground=WHITE, background=WHITE, arrowcolor=DARK, padding=3,
|
||||
bordercolor=RAND, lightcolor=WHITE, darkcolor=WHITE)
|
||||
st.map("TCombobox", fieldbackground=[("readonly", WHITE)], selectbackground=[("readonly", WHITE)],
|
||||
selectforeground=[("readonly", DARK)], bordercolor=[("focus", NAVY)])
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- Wort löschen
|
||||
@@ -423,6 +504,8 @@ class Sunken(tk.Frame):
|
||||
"""Eingelassener Rahmen (Doppellinie wie bei Win95-Listen)."""
|
||||
|
||||
def __init__(self, master, **kw):
|
||||
if MODERN:
|
||||
kw.update(bd=0, relief="flat", highlightthickness=1, highlightbackground=RAND)
|
||||
kw.setdefault("bd", 2)
|
||||
kw.setdefault("relief", "sunken")
|
||||
super().__init__(master, **kw)
|
||||
@@ -432,6 +515,15 @@ class Fenster(tk.Frame):
|
||||
"""Ein „Fenster im Fenster“ mit blauer Titelleiste – reine Optik, gliedert die Oberfläche."""
|
||||
|
||||
def __init__(self, master, titel: str, icon: tk.PhotoImage | None = None, **kw):
|
||||
if MODERN: # schlichte Überschrift statt blauer Titelleiste
|
||||
super().__init__(master, bd=0, bg=FACE, **kw)
|
||||
self.titel_var = tk.StringVar(value=titel)
|
||||
self.titelleiste = tk.Label(self, textvariable=self.titel_var, bg=FACE, fg=DARK, font=FONT_BOLD,
|
||||
anchor="w")
|
||||
self.titelleiste.pack(fill="x", pady=(0, 4))
|
||||
self.inhalt = tk.Frame(self, bg=FACE)
|
||||
self.inhalt.pack(fill="both", expand=True)
|
||||
return
|
||||
super().__init__(master, bd=2, relief="raised", bg=FACE, **kw)
|
||||
self.titelleiste = tk.Frame(self, bg=NAVY, height=18)
|
||||
self.titelleiste.pack(fill="x", padx=1, pady=1)
|
||||
@@ -453,7 +545,7 @@ class Statusleiste(tk.Frame):
|
||||
self.vars: dict[str, tk.StringVar] = {}
|
||||
for i, (key, breite) in enumerate(felder):
|
||||
v = tk.StringVar()
|
||||
lbl = tk.Label(self, textvariable=v, bd=1, relief="sunken", anchor="w", padx=4)
|
||||
lbl = tk.Label(self, textvariable=v, bd=1, relief="flat" if MODERN else "sunken", anchor="w", padx=4)
|
||||
if breite:
|
||||
lbl.configure(width=breite)
|
||||
lbl.pack(side="left", padx=(0, 2), pady=2)
|
||||
@@ -466,8 +558,11 @@ class Statusleiste(tk.Frame):
|
||||
|
||||
|
||||
def knopf(master, text: str, command=None, icon: tk.PhotoImage | None = None, breite: int | None = None, **kw):
|
||||
b = tk.Button(master, text=text, command=command, bd=2, relief="raised", bg=FACE,
|
||||
activebackground=FACE, **kw)
|
||||
if MODERN:
|
||||
b = tk.Button(master, text=text, command=command, **kw) # Aussehen aus apply()/_modern()
|
||||
else:
|
||||
b = tk.Button(master, text=text, command=command, bd=2, relief="raised", bg=FACE,
|
||||
activebackground=FACE, **kw)
|
||||
if icon is not None:
|
||||
b.configure(image=icon, compound="left", padx=6)
|
||||
if breite:
|
||||
@@ -482,6 +577,15 @@ class ToolbarButton(tk.Label):
|
||||
super().__init__(master, text=text, image=icon, compound="top", bd=1, relief="flat", bg=FACE,
|
||||
padx=6, pady=2, font=FONT)
|
||||
self.command = command
|
||||
if MODERN: # Hervorhebung durch Farbe statt 3D-Kante
|
||||
self.configure(bd=0, padx=8, pady=4)
|
||||
self.bind("<Enter>", lambda e: self.configure(bg=LIGHT))
|
||||
self.bind("<Leave>", lambda e: self.configure(bg=FACE))
|
||||
self.bind("<ButtonPress-1>", lambda e: self.configure(bg=SEL_BG))
|
||||
self.bind("<ButtonRelease-1>", self._release)
|
||||
if tooltip:
|
||||
Tooltip(self, tooltip)
|
||||
return
|
||||
self.bind("<Enter>", lambda e: self.configure(relief="raised"))
|
||||
self.bind("<Leave>", lambda e: self.configure(relief="flat"))
|
||||
self.bind("<ButtonPress-1>", lambda e: self.configure(relief="sunken"))
|
||||
@@ -490,7 +594,7 @@ class ToolbarButton(tk.Label):
|
||||
Tooltip(self, tooltip)
|
||||
|
||||
def _release(self, e):
|
||||
self.configure(relief="raised")
|
||||
self.configure(bg=LIGHT) if MODERN else self.configure(relief="raised")
|
||||
if 0 <= e.x < self.winfo_width() and 0 <= e.y < self.winfo_height() and self.command:
|
||||
self.command()
|
||||
|
||||
@@ -528,6 +632,10 @@ class Tooltip:
|
||||
|
||||
def trenner(master, vertikal: bool = True):
|
||||
f = tk.Frame(master, bg=FACE)
|
||||
if MODERN:
|
||||
tk.Frame(f, width=1, height=1, bg=RAND).pack(side="left", fill="y") if vertikal else \
|
||||
tk.Frame(f, height=1, bg=RAND).pack(fill="x")
|
||||
return f
|
||||
if vertikal:
|
||||
tk.Frame(f, width=1, bg=SHADOW).pack(side="left", fill="y")
|
||||
tk.Frame(f, width=1, bg=HILITE).pack(side="left", fill="y")
|
||||
@@ -538,6 +646,8 @@ def trenner(master, vertikal: bool = True):
|
||||
|
||||
|
||||
def gruppe(master, titel: str, **kw) -> tk.LabelFrame:
|
||||
if MODERN:
|
||||
return tk.LabelFrame(master, text=titel, bd=0, relief="flat", bg=FACE, padx=4, pady=2, font=FONT_BOLD, **kw)
|
||||
return tk.LabelFrame(master, text=titel, bd=2, relief="groove", bg=FACE, padx=6, pady=4, **kw)
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user