- selbst gezeichneter Fensterrahmen (verschieben, maximieren, minimieren, Größe ändern), abschaltbar - Dialoge mit Win95-Titelleiste; vorherige Modalsperre wird nach Rückfragen wiederhergestellt - Assistent: Speicherort, Unterhändler (Nummern aus Abrechnung vorschlagen), erste Abrechnung - Rundgang mit Hinweis-Sprechblasen, Tipp des Tages Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
629 lines
20 KiB
Python
629 lines
20 KiB
Python
"""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....
|
||
................""",
|
||
"birne": """
|
||
.....kkkkk......
|
||
....kyyyyyk.....
|
||
...kyywwyyyk....
|
||
..kyywwyyyyyk...
|
||
..kywwyyyyyyk...
|
||
..kyyyyyyyyyk...
|
||
..kyyyyyyyyyk...
|
||
...kyyyyyyyk....
|
||
....kyyyyyk.....
|
||
.....kyyyk......
|
||
.....kdddk......
|
||
.....kgggk......
|
||
.....kdddk......
|
||
.....kgggk......
|
||
......kkk.......
|
||
................""",
|
||
"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
|
||
|
||
# Klassischer Win95-Rahmen auch für Dialoge (wird von der App gesetzt, abschaltbar in den Einstellungen)
|
||
KLASSISCH = False
|
||
|
||
|
||
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
|
||
self.titelleiste = None
|
||
if KLASSISCH:
|
||
from provtool.gui.rahmen import Titelleiste
|
||
self.overrideredirect(True)
|
||
self.configure(bd=2, relief="raised", padx=2, pady=2)
|
||
self.titelleiste = Titelleiste(self, self, titel, None, ("close",), schliessen=self.abbrechen)
|
||
self.titelleiste.pack(side="top", fill="x")
|
||
|
||
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"+{max(0, x)}+{max(0, y)}")
|
||
self.deiconify()
|
||
self.lift()
|
||
vorher = self.grab_current() # z. B. der Assistent, wenn hier eine Rückfrage aufgeht
|
||
try:
|
||
self.grab_set()
|
||
except tk.TclError:
|
||
pass
|
||
bind_id = None
|
||
if KLASSISCH:
|
||
# Ein rahmenloser Dialog darf nicht hinter dem Hauptfenster verschwinden
|
||
bind_id = m.bind("<FocusIn>", lambda e: self.winfo_exists() and self.lift(), add="+")
|
||
self.focus_force()
|
||
(fokus or self).focus_set()
|
||
self.wait_window()
|
||
if bind_id:
|
||
try:
|
||
m.unbind("<FocusIn>", bind_id)
|
||
except tk.TclError:
|
||
pass
|
||
# Tk gibt beim Schließen die Sperre frei, stellt die vorige aber nicht wieder her
|
||
try:
|
||
if vorher is not None and vorher.winfo_exists():
|
||
vorher.grab_set()
|
||
vorher.focus_force()
|
||
except tk.TclError:
|
||
pass
|
||
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)
|