Provisions-Manager 95: Import, Händler-DB, Auszahlungsregeln, Excel-Export, Win95-Oberfläche, Wine-Build

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-11-02 09:24:39 +01:00
commit da2e3c9ba1
25 files changed
+3366

No files matched your search

+582
View File
@@ -0,0 +1,582 @@
"""Windows-95-Optik für Tkinter: Farben, Schriften, Pixel-Icons und ein paar Bausteine."""
from __future__ import annotations
import sys
import tkinter as tk
from tkinter import ttk
FACE = "#c0c0c0"
HILITE = "#ffffff"
LIGHT = "#dfdfdf"
SHADOW = "#808080"
DARK = "#000000"
NAVY = "#000080"
TEAL = "#008080"
WHITE = "#ffffff"
INFO_BG = "#ffffe1"
IS_WIN = sys.platform.startswith("win")
FONT_FAMILY = "MS Sans Serif" if IS_WIN else "DejaVu Sans"
FONT_SIZE = 8 if IS_WIN else 9
FONT = (FONT_FAMILY, FONT_SIZE)
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)
# --------------------------------------------------------------------------- Pixel-Icons (16x16)
PALETTE = {
"k": "#000000", "w": "#ffffff", "g": "#c0c0c0", "d": "#808080", "y": "#ffff00", "Y": "#808000",
"b": "#000080", "B": "#0000ff", "r": "#ff0000", "R": "#800000", "G": "#008000", "L": "#00ff00",
"t": "#008080", "c": "#00ffff", "s": "#ffcc99", "o": "#ff8000",
}
ICONS = {
"neu": """
..kkkkkkkkk.....
..kwwwwwwwkk....
..kwwwwwwwkwk...
..kwwwwwwwkkkk..
..kwwwwwwwwwwk..
..kwkkkkkkkwwk..
..kwwwwwwwwwwk..
..kwkkkkkkkkwk..
..kwwwwwwwwwwk..
..kwkkkkkkkkwk..
..kwwwwwwwwwwk..
..kwkkkkkwwwwk..
..kwwwwwwwwwwk..
..kwwwwwwwwwwk..
..kkkkkkkkkkkk..
................""",
"oeffnen": """
................
.........kkk....
........k...k.k.
.............kk.
.kkk........kkk.
kyYyk...........
kYyYykkkkkkk....
kyYyYyYyYyYk....
kYyYykkkkkkkkkk.
kyYykyyyyyyyyyk.
kYykyyyyyyyyyk..
kykyyyyyyyyyk...
kkyyyyyyyyyk....
kkkkkkkkkkk.....
................
................""",
"import": """
......kkkk......
......kGGk......
......kGGk......
......kGGk......
...kkkkGGkkkk...
....kGGGGGGk....
.....kGGGGk.....
......kGGk......
.......kk.......
.kkkkkkkkkkkkkk.
.kwwwwwwwwwwwdk.
.kwkkwkkwkkwwdk.
.kwwwwwwwwwwwdk.
.kddddddddddddk.
.kkkkkkkkkkkkkk.
................""",
"export": """
kkkkkkkkkkkkkkk.
kbbkwwwwwwwkbbk.
kbbkwwwwwwwkbbk.
kbbkwwwwwwwkbbk.
kbbkwwwwwwwkbbk.
kbbkkkkkkkkkbbk.
kbbbbbbbbbbbbbk.
kbbbbbbbbbbbbbk.
kbbkkkkkkkkkkbk.
kbbkgggkkgggkbk.
kbbkgggkkgggkbk.
kbbkgggkkgggkbk.
kbbkgggggggggbk.
.kbkkkkkkkkkkbk.
..kkkkkkkkkkkkk.
................""",
"haendler": """
................
....kkk.........
...kssskk.kkk...
...ksksk.kssk...
...kssskksksk...
....kskkkssk....
...kbbbkkksk....
..kbbbbbkRRRk...
..kbbbbbkRRRRk..
..kbkbkbRRRRRk..
..kbkbkbkRkRRk..
..kbkbkbkRkRRk..
..kbbbbbkRRRRk..
...kbkbkkRkRk...
...kkkkk.kkkk...
................""",
"prozent": """
................
..kkk......kk...
.kwwwk....kk....
.kwkwk...kk.....
.kwwwk..kk......
..kkk..kk.......
......kk........
.....kk.........
....kk..........
...kk...kkk.....
..kk...kwwwk....
.kk....kwkwk....
kk.....kwwwk....
........kkk.....
................
................""",
"rechner": """
.kkkkkkkkkkkkk..
.kddddddddddddk.
.kdkkkkkkkkkkdk.
.kdkLLLLLLLLkdk.
.kdkkkkkkkkkkdk.
.kddddddddddddk.
.kdwwdwwdwwdrrk.
.kdkkdkkdkkdkkk.
.kddddddddddddk.
.kdwwdwwdwwdwwk.
.kdkkdkkdkkdkkk.
.kddddddddddddk.
.kdwwdwwdwwdwwk.
.kdkkdkkdkkdkkk.
.kkkkkkkkkkkkkk.
................""",
"info": """
.....kkkkkk.....
...kkBBBBBBkk...
..kBBBBBBBBBBk..
.kBBBBBwwBBBBBk.
.kBBBBBwwBBBBBk.
kBBBBBBBBBBBBBBk
kBBBBBwwwBBBBBBk
kBBBBBBwwBBBBBBk
kBBBBBBwwBBBBBBk
kBBBBBBwwBBBBBBk
.kBBBBBwwBBBBBk.
.kBBBBwwwwBBBBk.
..kBBBBBBBBBBk..
...kkBBBBBBkk...
.....kkkkkk.....
................""",
"warnung": """
.......kk.......
......kyyk......
......kyyk......
.....kyyyyk.....
.....kykkyk.....
....kyykkyyk....
....kyykkyyk....
...kyyykkyyyk...
...kyyykkyyyk...
..kyyyyyyyyyyk..
..kyyyykkyyyyk..
.kyyyyykkyyyyyk.
.kyyyyyyyyyyyyk.
kkkkkkkkkkkkkkkk
................
................""",
"fehler": """
.....kkkkkk.....
...kkrrrrrrkk...
..krrrrrrrrrrk..
.krrwrrrrrrwrrk.
.krwwwrrrrwwwrk.
krrrwwwrrwwwrrrk
krrrrwwwwwwrrrrk
krrrrrwwwwrrrrrk
krrrrrwwwwrrrrrk
krrrrwwwwwwrrrrk
.krrwwwrrwwwrrk.
.krwwwrrrrwwwrk.
..krrrrrrrrrrk..
...kkrrrrrrkk...
.....kkkkkk.....
................""",
"frage": """
.....kkkkkk.....
...kkwwwwwwkk...
..kwwwwwwwwwwk..
.kwwwwBBBBwwwwk.
.kwwwBBwwBBwwwk.
kwwwwwwwwBBwwwwk
kwwwwwwwBBwwwwwk
kwwwwwwBBwwwwwwk
kwwwwwwBBwwwwwwk
kwwwwwwwwwwwwwwk
.kwwwwwBBwwwwwk.
.kwwwwwBBwwwwwk.
..kwwwwwwwwwwk..
...kkwwwwwwkk...
.....kkkkkkk....
................""",
"app": """
................
.....kkkkkkk....
....kdddddddk...
....kdkkkkkdk...
....kdkLLLkdk...
....kdkLLLkdk...
....kdkkkkkdk...
....kdwdwdwdk...
....kdddddddk...
....kdwdwdwdk...
....kdddddddk...
..ttkdwdwdwdktt.
.tcccckkkkkkccct
tcccccyyyyyyccct
.ttttttttttttttt
................""",
}
def _icon_rows(name: str) -> list[str]:
rows = [r.strip() for r in ICONS[name].strip("\n").splitlines() if r.strip()]
return [r.ljust(16, ".")[:16] for r in rows[:16]] + ["." * 16] * (16 - len(rows))
def make_icon(master, name: str, scale: int = 1) -> tk.PhotoImage:
img = tk.PhotoImage(master=master, width=16 * scale, height=16 * scale)
for y, row in enumerate(_icon_rows(name)):
for x, ch in enumerate(row):
if ch == ".":
continue
color = PALETTE[ch]
img.put(color, to=(x * scale, y * scale, (x + 1) * scale, (y + 1) * scale))
return img
class Icons:
"""Lazy-Cache, damit PhotoImages nicht vom Garbage Collector entsorgt werden."""
def __init__(self, master):
self.master = master
self._cache: dict[tuple[str, int], tk.PhotoImage] = {}
def __call__(self, name: str, scale: int = 1) -> tk.PhotoImage:
key = (name, scale)
if key not in self._cache:
self._cache[key] = make_icon(self.master, name, scale)
return self._cache[key]
# --------------------------------------------------------------------------- Theme
def apply(root: tk.Tk) -> None:
root.configure(bg=FACE)
o = root.option_add
o("*Font", FONT)
o("*Background", FACE)
o("*Foreground", DARK)
o("*activeBackground", FACE)
o("*highlightThickness", 0)
o("*selectBackground", NAVY)
o("*selectForeground", WHITE)
o("*Button.relief", "raised")
o("*Button.borderWidth", 2)
o("*Button.padX", 8)
o("*Button.padY", 1)
o("*Entry.background", WHITE)
o("*Entry.relief", "sunken")
o("*Entry.borderWidth", 2)
o("*Entry.disabledBackground", FACE)
o("*Text.background", WHITE)
o("*Text.relief", "sunken")
o("*Text.borderWidth", 2)
o("*Listbox.background", WHITE)
o("*Listbox.relief", "sunken")
o("*Listbox.borderWidth", 2)
o("*Checkbutton.selectColor", WHITE)
o("*Radiobutton.selectColor", WHITE)
o("*Menu.relief", "raised")
o("*Menu.activeBackground", NAVY)
o("*Menu.activeForeground", WHITE)
o("*Menu.activeBorderWidth", 0)
o("*TCombobox*Listbox.background", WHITE)
o("*TCombobox*Listbox.selectBackground", NAVY)
from tkinter import font as tkfont
zeile = max(18, tkfont.Font(root=root, font=FONT).metrics("linespace") + 5)
st = ttk.Style(root)
st.theme_use("classic" if "classic" in st.theme_names() else "default")
st.configure(".", background=FACE, foreground=DARK, font=FONT, troughcolor=LIGHT,
selectbackground=NAVY, selectforeground=WHITE, fieldbackground=WHITE,
bordercolor=FACE, lightcolor=HILITE, darkcolor=SHADOW, highlightthickness=0)
st.configure("Treeview", background=WHITE, fieldbackground=WHITE, rowheight=zeile, borderwidth=0, font=FONT)
st.map("Treeview", background=[("selected", NAVY)], foreground=[("selected", WHITE)])
st.configure("Treeview.Heading", background=FACE, relief="raised", borderwidth=2, font=FONT, padding=(3, 1))
st.map("Treeview.Heading", relief=[("pressed", "sunken")], background=[("active", FACE)])
st.configure("TScrollbar", background=FACE, troughcolor=LIGHT, arrowcolor=DARK, relief="raised", borderwidth=2,
arrowsize=16, width=16)
st.map("TScrollbar", background=[("active", FACE)])
st.configure("TCombobox", fieldbackground=WHITE, background=FACE, arrowsize=14, padding=1)
st.map("TCombobox", fieldbackground=[("readonly", WHITE)], selectbackground=[("readonly", WHITE)],
selectforeground=[("readonly", DARK)])
st.configure("TPanedwindow", background=FACE)
st.configure("Sash", sashthickness=6, background=FACE)
# --------------------------------------------------------------------------- Bausteine
class Sunken(tk.Frame):
"""Eingelassener Rahmen (Doppellinie wie bei Win95-Listen)."""
def __init__(self, master, **kw):
kw.setdefault("bd", 2)
kw.setdefault("relief", "sunken")
super().__init__(master, **kw)
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):
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)
if icon is not None:
tk.Label(self.titelleiste, image=icon, bg=NAVY).pack(side="left", padx=(2, 0))
self.titel_var = tk.StringVar(value=titel)
tk.Label(self.titelleiste, textvariable=self.titel_var, bg=NAVY, fg=WHITE, font=FONT_BOLD,
anchor="w").pack(side="left", fill="x", padx=3)
self.inhalt = tk.Frame(self, bg=FACE)
self.inhalt.pack(fill="both", expand=True, padx=2, pady=2)
def set_titel(self, t: str):
self.titel_var.set(t)
class Statusleiste(tk.Frame):
def __init__(self, master, felder: list[tuple[str, int]]):
super().__init__(master, bg=FACE)
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)
if breite:
lbl.configure(width=breite)
lbl.pack(side="left", padx=(0, 2), pady=2)
else:
lbl.pack(side="left", fill="x", expand=True, padx=(0, 2), pady=2)
self.vars[key] = v
def set(self, key: str, text: str):
self.vars[key].set(text)
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 icon is not None:
b.configure(image=icon, compound="left", padx=6)
if breite:
b.configure(width=breite)
return b
class ToolbarButton(tk.Label):
"""Flacher Werkzeugleisten-Knopf (Office-95-Stil): hebt sich beim Überfahren, senkt sich beim Klick."""
def __init__(self, master, text: str, icon: tk.PhotoImage, command, tooltip: str = ""):
super().__init__(master, text=text, image=icon, compound="top", bd=1, relief="flat", bg=FACE,
padx=6, pady=2, font=FONT)
self.command = command
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"))
self.bind("<ButtonRelease-1>", self._release)
if tooltip:
Tooltip(self, tooltip)
def _release(self, e):
self.configure(relief="raised")
if 0 <= e.x < self.winfo_width() and 0 <= e.y < self.winfo_height() and self.command:
self.command()
class Tooltip:
def __init__(self, widget, text: str):
self.widget, self.text, self.tip, self._after = widget, text, None, None
widget.bind("<Enter>", self._plan, add="+")
widget.bind("<Leave>", self._weg, add="+")
widget.bind("<ButtonPress>", self._weg, add="+")
def _plan(self, _e):
self._after = self.widget.after(600, self._zeigen)
def _zeigen(self):
x = self.widget.winfo_rootx() + 10
y = self.widget.winfo_rooty() + self.widget.winfo_height() + 4
self.tip = tw = tk.Toplevel(self.widget)
tw.wm_overrideredirect(True)
tw.wm_geometry(f"+{x}+{y}")
tk.Label(tw, text=self.text, bg=INFO_BG, bd=1, relief="solid", padx=4, pady=1).pack()
def _weg(self, _e=None):
if self._after:
self.widget.after_cancel(self._after)
self._after = None
if self.tip:
self.tip.destroy()
self.tip = None
def trenner(master, vertikal: bool = True):
f = tk.Frame(master, bg=FACE)
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")
else:
tk.Frame(f, height=1, bg=SHADOW).pack(fill="x")
tk.Frame(f, height=1, bg=HILITE).pack(fill="x")
return f
def gruppe(master, titel: str, **kw) -> tk.LabelFrame:
return tk.LabelFrame(master, text=titel, bd=2, relief="groove", bg=FACE, padx=6, pady=4, **kw)
def scroll_tree(master, **kw) -> tuple[tk.Frame, ttk.Treeview]:
rahmen = Sunken(master, bg=WHITE)
tree = ttk.Treeview(rahmen, **kw)
vs = ttk.Scrollbar(rahmen, orient="vertical", command=tree.yview)
hs = ttk.Scrollbar(rahmen, orient="horizontal", command=tree.xview)
tree.configure(yscrollcommand=vs.set, xscrollcommand=hs.set)
tree.grid(row=0, column=0, sticky="nsew")
vs.grid(row=0, column=1, sticky="ns")
hs.grid(row=1, column=0, sticky="ew")
rahmen.rowconfigure(0, weight=1)
rahmen.columnconfigure(0, weight=1)
return rahmen, tree
def auto_breite(tree: ttk.Treeview, flex: dict[str, float], reserve: int = 4):
"""Feste Spalten behalten ihre Breite, die „flex“-Spalten teilen sich den Rest anteilig.
So bleiben z. B. die Betragsspalten immer sichtbar, egal wie breit das Fenster ist."""
def anpassen(_e=None):
verfuegbar = tree.winfo_width() - reserve
if verfuegbar < 50:
return
alle = ["#0"] + list(tree["columns"]) if "tree" in str(tree["show"]) else list(tree["columns"])
fest = sum(int(tree.column(c, "width")) for c in alle if c not in flex)
rest = max(0, verfuegbar - fest)
summe = sum(flex.values())
for c, anteil in flex.items():
minimum = int(tree.column(c, "minwidth"))
tree.column(c, width=max(minimum, int(rest * anteil / summe)))
tree.bind("<Configure>", anpassen, add="+")
return anpassen
# --------------------------------------------------------------------------- Dialoge
class Dialog(tk.Toplevel):
"""Modaler Dialog im Win95-Stil."""
def __init__(self, master, titel: str, resizable: bool = False):
super().__init__(master, bg=FACE)
self.withdraw()
self.title(titel)
self.transient(master.winfo_toplevel())
self.resizable(resizable, resizable)
self.result = None
self.protocol("WM_DELETE_WINDOW", self.abbrechen)
self.bind("<Escape>", lambda e: self.abbrechen())
try:
self.iconphoto(False, master.winfo_toplevel()._app_icon) # type: ignore[attr-defined]
except Exception:
pass
def zeigen(self, fokus=None):
self.update_idletasks()
m = self.master.winfo_toplevel()
w, h = self.winfo_reqwidth(), self.winfo_reqheight()
x = m.winfo_rootx() + max(0, (m.winfo_width() - w) // 2)
y = m.winfo_rooty() + max(0, (m.winfo_height() - h) // 3)
self.geometry(f"+{x}+{y}")
self.deiconify()
self.lift()
try:
self.grab_set()
except tk.TclError:
pass
(fokus or self).focus_set()
self.wait_window()
return self.result
def abbrechen(self):
self.result = None
self.destroy()
def knopfleiste(self, knoepfe: list[tuple[str, object]], default: int = 0) -> tk.Frame:
f = tk.Frame(self, bg=FACE)
for i, (text, cmd) in enumerate(knoepfe):
b = knopf(f, text, cmd, breite=10)
if i == default:
b.configure(default="active")
b.pack(side="left", padx=4)
return f
def _meldung(master, titel: str, text: str, icon: str, knoepfe: list[tuple[str, object]], default=0):
d = Dialog(master, titel)
body = tk.Frame(d, bg=FACE)
body.pack(fill="both", expand=True, padx=12, pady=(14, 8))
ic = make_icon(d, icon, 2)
d._ic = ic
tk.Label(body, image=ic, bg=FACE).pack(side="left", anchor="n", padx=(0, 14))
tk.Label(body, text=text, bg=FACE, justify="left", wraplength=420, anchor="w").pack(side="left", fill="x")
def mk(val):
def f():
d.result = val
d.destroy()
return f
leiste = d.knopfleiste([(t, mk(v)) for t, v in knoepfe], default)
leiste.pack(pady=(4, 12))
d.bind("<Return>", lambda e: mk(knoepfe[default][1])())
return d.zeigen()
def info(master, titel: str, text: str):
_meldung(master, titel, text, "info", [("OK", True)])
def warnung(master, titel: str, text: str):
_meldung(master, titel, text, "warnung", [("OK", True)])
def fehler(master, titel: str, text: str):
_meldung(master, titel, text, "fehler", [("OK", True)])
def frage(master, titel: str, text: str, ja: str = "Ja", nein: str = "Nein", default_ja: bool = True) -> bool:
return bool(_meldung(master, titel, text, "frage", [(ja, True), (nein, False)], 0 if default_ja else 1))
def eingabe(master, titel: str, text: str, wert: str = "") -> str | None:
d = Dialog(master, titel)
tk.Label(d, text=text, bg=FACE, justify="left", anchor="w").pack(fill="x", padx=12, pady=(12, 4))
var = tk.StringVar(value=wert)
e = tk.Entry(d, textvariable=var, width=46)
e.pack(padx=12, pady=4)
e.select_range(0, "end")
def ok():
d.result = var.get()
d.destroy()
d.bind("<Return>", lambda _e: ok())
d.knopfleiste([("OK", ok), ("Abbrechen", d.abbrechen)]).pack(pady=(6, 12))
return d.zeigen(e)