Eigene Kennungen ausblenden, Win95-Menüs, verschiebbares Hauptfenster bei offenem Dialog u. a.
Windows-Build / windows-exe (push) Canceled after 53m28s

Neu:
- Eigene Kennungen (eigene Filiale) ausblenden: nicht angezeigt, nicht mitgerechnet, nicht
  exportiert; per Rechtsklick, Händler-Menü oder Import-Bericht; Händler-Zuordnung hat Vorrang
- Menüs im Win95-Stil: wechseln beim Überfahren der Menüleiste, Tastatur, Kontextmenü gleich
- Dialoge sperren nur den Inhalt: Hauptfenster bleibt verschiebbar/maximierbar; Dialoge haben
  eigenen Taskleisten-Eintrag; Klick auf gesperrten Inhalt holt den Dialog nach vorn (blinkt)
- Trennlinie zwischen Händler- und Positionsliste sichtbar und ziehbar, Position wird gemerkt
- Strg+Rücktaste / Strg+Entf löschen ganze Wörter in allen Eingabefeldern

Behoben:
- Gedrückte Knöpfe zeigten keine Beschriftung (activeForeground gehört in Tk zur Klasse Background)
- Tipp des Tages: Text lief aus dem sichtbaren Bereich
- Größenänderung: breiterer Griffbereich mit Ecken, Zeiger wird zuverlässig zurückgesetzt,
  flüssiger; Verschieben über Windows selbst (inkl. Andocken), Minimieren über Windows
- Zeichen, die MS Sans Serif nicht hat (…, ▸, →, •), durch Win95-übliche ersetzt
- Menü/Tooltip/Dialog: Größe+Position zusammen setzen (Tk ignoriert sonst die Position)
- Suche reagiert erst nach kurzer Tipp-Pause (flüssigeres Tippen)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-11-02 12:58:18 +01:00
1 parent 69c2a108a9
commit 807a38622c
15 files changed
+1217 -187

No files matched your search

+298
View File
@@ -0,0 +1,298 @@
"""Menüs im Windows-95-Stil (Menüleiste, Aufklapp- und Kontextmenüs).
Die nativen Windows-Menüs sind weiß und wechseln nicht mit, wenn man bei offenem Menü mit der
Maus zum nächsten Menüleisten-Eintrag fährt. Diese Menüs sind selbst gezeichnet:
- grau, Markierung in Navy, Tastenkürzel rechtsbündig, Trennlinien eingelassen
- bei offenem Menü wechselt das Menü beim Überfahren der Menüleiste
- Tastatur: Pfeil hoch/runter wählen, Enter ausführen, Esc schließen, Pfeil links/rechts benachbartes Menü
- Klick daneben oder Wechsel in ein anderes Programm schließt das Menü
Die Schnittstelle (add_command, add_separator, tk_popup) entspricht tk.Menu.
"""
from __future__ import annotations
import tkinter as tk
from provtool.gui import win95 as w
class Popup:
"""Ein Aufklapp- bzw. Kontextmenü."""
def __init__(self, master: tk.Misc):
self.master = master
self.eintraege: list[dict] = []
self.fenster: tk.Toplevel | None = None
self.zeilen: list[tuple[dict, tk.Frame, list[tk.Label]]] = []
self.aktiv = -1
self.leiste: Menueleiste | None = None
self._pruefer = None
# ------------------------------------------------------------- tk.Menu-kompatibel
def add_command(self, label: str, command=None, accelerator: str = "", state: str = "normal"):
self.eintraege.append(dict(art="befehl", label=label, command=command, accelerator=accelerator,
state=state))
def add_separator(self):
self.eintraege.append(dict(art="trenner"))
def tk_popup(self, x: int, y: int):
self.oeffnen(x, y)
# ------------------------------------------------------------- Anzeige
@property
def offen(self) -> bool:
try:
return self.fenster is not None and bool(self.fenster.winfo_exists())
except tk.TclError:
return False
def oeffnen(self, x: int, y: int, leiste: Menueleiste | None = None):
self.schliessen()
self.leiste = leiste
root = self.master._root()
f = self.fenster = tk.Toplevel(root, bg=w.FACE, bd=2, relief="raised")
f.withdraw()
f.overrideredirect(True)
self.zeilen = []
for e in self.eintraege:
if e["art"] == "trenner":
t = tk.Frame(f, bg=w.FACE, height=8)
t.pack(fill="x")
linie = w.trenner(t, vertikal=False)
linie.place(relx=0, rely=0.5, relwidth=1, y=-1)
continue
zeile = tk.Frame(f, bg=w.FACE)
zeile.pack(fill="x")
grau = e["state"] == "disabled"
text = tk.Label(zeile, text=e["label"], bg=w.FACE, anchor="w", padx=18, pady=2,
fg=w.SHADOW if grau else w.DARK)
text.pack(side="left", fill="x", expand=True)
labels = [text]
if e["accelerator"]:
acc = tk.Label(zeile, text=e["accelerator"], bg=w.FACE, anchor="e", padx=12, pady=2,
fg=w.SHADOW if grau else w.DARK)
acc.pack(side="right")
labels.append(acc)
idx = len(self.zeilen)
for wdg in [zeile] + labels:
wdg.bind("<Enter>", lambda ev, i=idx: self._markieren(i))
wdg.bind("<ButtonRelease-1>", lambda ev, i=idx: self._ausfuehren(i))
self.zeilen.append((e, zeile, labels))
f.update_idletasks()
bw, bh = f.winfo_reqwidth(), f.winfo_reqheight()
sw, sh = f.winfo_screenwidth(), f.winfo_screenheight()
x = max(0, min(x, sw - bw))
y = max(0, min(y, sh - bh))
# Größe UND Position zusammen setzen: eine reine Positionsangabe ignoriert Tk unter Windows bei
# rahmenlosen Fenstern – das Menü landete sonst oben links in der Bildschirmecke.
f.geometry(f"{bw}x{bh}+{x}+{y}")
f.deiconify()
f.lift()
f.update_idletasks()
try:
from provtool.gui import win32
win32.besitzer_setzen(f, root) # bleibt über dem Hauptfenster
except Exception:
pass
# Alle Maus-Ereignisse der Anwendung an das Menü (lokale Tk-Sperre): Klick daneben schließt,
# Bewegung über die Menüleiste wechselt das Menü.
try:
f.grab_set()
except tk.TclError:
pass
f.focus_force()
f.bind("<ButtonPress>", self._klick)
f.bind("<Motion>", self._bewegung)
f.bind("<Escape>", lambda e: self.schliessen())
f.bind("<Up>", lambda e: self._schritt(-1))
f.bind("<Down>", lambda e: self._schritt(1))
f.bind("<Return>", lambda e: self._ausfuehren(self.aktiv))
f.bind("<KP_Enter>", lambda e: self._ausfuehren(self.aktiv))
f.bind("<Left>", lambda e: self.leiste and self.leiste.nachbar(-1))
f.bind("<Right>", lambda e: self.leiste and self.leiste.nachbar(1))
self.aktiv = -1
self._pruefer = f.after(250, self._fokus_pruefen)
def schliessen(self):
if self._pruefer is not None and self.fenster is not None:
try:
self.fenster.after_cancel(self._pruefer)
except tk.TclError:
pass
self._pruefer = None
if self.offen:
try:
self.fenster.grab_release()
except tk.TclError:
pass
self.fenster.destroy()
self.fenster = None
if self.leiste is not None:
self.leiste._geschlossen(self)
# ------------------------------------------------------------- Bedienung
def _innerhalb(self, x_root: int, y_root: int) -> bool:
"""Liegt der Punkt über diesem Menü? Fragt das Fenstersystem, welches Fenster dort liegt –
die Geometrie rahmenloser Fenster meldet Tk unter Windows nicht zuverlässig."""
f = self.fenster
try:
unter = f.winfo_containing(x_root, y_root)
except (tk.TclError, KeyError):
unter = None
if unter is not None:
return unter.winfo_toplevel() is f
return (f.winfo_rootx() <= x_root < f.winfo_rootx() + f.winfo_width()
and f.winfo_rooty() <= y_root < f.winfo_rooty() + f.winfo_height())
def _klick(self, e):
if self._innerhalb(e.x_root, e.y_root):
return
if self.leiste is not None and self.leiste.knopf_bei(e.x_root, e.y_root) is not None:
knopf = self.leiste.knopf_bei(e.x_root, e.y_root)
if knopf is self.leiste.offener_knopf:
self.schliessen() # Klick auf den eigenen Menütitel klappt zu
else:
self.leiste.oeffnen(knopf)
return "break"
self.schliessen()
return "break"
def _bewegung(self, e):
if self.leiste is None or self._innerhalb(e.x_root, e.y_root):
return
knopf = self.leiste.knopf_bei(e.x_root, e.y_root)
if knopf is not None and knopf is not self.leiste.offener_knopf:
self.leiste.oeffnen(knopf)
def _markieren(self, i: int):
self.aktiv = i
for j, (e, zeile, labels) in enumerate(self.zeilen):
an = j == i and e.get("state") != "disabled"
bg = w.NAVY if an else w.FACE
fg = w.WHITE if an else (w.SHADOW if e.get("state") == "disabled" else w.DARK)
zeile.configure(bg=bg)
for lbl in labels:
lbl.configure(bg=bg, fg=fg)
def _schritt(self, richtung: int):
if not self.zeilen:
return
i = self.aktiv
for _ in range(len(self.zeilen)):
i = (i + richtung) % len(self.zeilen)
if self.zeilen[i][0].get("state") != "disabled":
break
self._markieren(i)
def _ausfuehren(self, i: int):
if not (0 <= i < len(self.zeilen)):
return
e = self.zeilen[i][0]
if e.get("state") == "disabled":
return
self.schliessen()
if e.get("command"):
# nach dem Schließen ausführen (der Befehl öffnet evtl. einen Dialog)
self.master.after_idle(e["command"])
def _fokus_pruefen(self):
"""In ein anderes Programm gewechselt -> Menü schließen."""
self._pruefer = None
if not self.offen:
return
try:
weg = self.fenster.focus_get() is None
except (tk.TclError, KeyError):
weg = False
if weg:
self.schliessen()
else:
self._pruefer = self.fenster.after(250, self._fokus_pruefen)
class Menueleiste(tk.Frame):
"""Graue Menüleiste mit Popup-Menüs (die native von Windows 10/11 ist weiß)."""
def __init__(self, master, root: tk.Misc):
super().__init__(master, bg=w.FACE)
self.root = root
self.knoepfe: dict[str, tk.Label] = {}
self.menues: dict[tk.Label, Popup] = {}
self.offener_knopf: tk.Label | None = None
def menue(self, label: str, underline: int | None = 0) -> Popup:
knopf = tk.Label(self, text=label, bg=w.FACE, padx=7, pady=2, bd=1, relief="flat",
underline=-1 if underline is None else underline)
knopf.pack(side="left")
popup = Popup(self)
self.knoepfe[label] = knopf
self.menues[knopf] = popup
knopf.bind("<ButtonPress-1>", lambda e, k=knopf: self._klick(k))
knopf.bind("<Enter>", lambda e, k=knopf: self._hover(k, True))
knopf.bind("<Leave>", lambda e, k=knopf: self._hover(k, False))
if underline is not None:
taste = label[underline].lower()
self.root.bind_all(f"<Alt-KeyPress-{taste}>", lambda e, k=knopf: self._alt(k))
return popup
# ---- Zustand
def knopf_bei(self, x_root: int, y_root: int) -> tk.Label | None:
for k in self.knoepfe.values():
if (k.winfo_rootx() <= x_root < k.winfo_rootx() + k.winfo_width()
and k.winfo_rooty() <= y_root < k.winfo_rooty() + k.winfo_height()):
return k
return None
def _darstellen(self):
for k in self.knoepfe.values():
if k is self.offener_knopf:
k.configure(bg=w.NAVY, fg=w.WHITE, relief="flat")
else:
k.configure(bg=w.FACE, fg=w.DARK, relief="flat")
# ---- Bedienung
def oeffnen(self, knopf: tk.Label):
from provtool.gui import modal
if modal.offen():
return
alt = self.offener_knopf
if alt is not None and alt is not knopf:
self.menues[alt].leiste = None # ohne Rückmeldung schließen, wir öffnen gleich das nächste
self.menues[alt].schliessen()
self.offener_knopf = knopf
self._darstellen()
self.menues[knopf].oeffnen(knopf.winfo_rootx(), knopf.winfo_rooty() + knopf.winfo_height(), leiste=self)
def schliessen(self):
if self.offener_knopf is not None:
self.menues[self.offener_knopf].schliessen()
def _geschlossen(self, popup: Popup):
if self.offener_knopf is not None and self.menues.get(self.offener_knopf) is popup:
self.offener_knopf = None
self._darstellen()
def nachbar(self, richtung: int):
liste = list(self.knoepfe.values())
if self.offener_knopf in liste:
i = (liste.index(self.offener_knopf) + richtung) % len(liste)
self.oeffnen(liste[i])
def _klick(self, knopf):
if self.offener_knopf is knopf:
self.schliessen()
else:
self.oeffnen(knopf)
return "break"
def _hover(self, knopf, drin: bool):
if self.offener_knopf is None:
knopf.configure(relief="raised" if drin else "flat")
def _alt(self, knopf):
self.oeffnen(knopf)
if self.offener_knopf is knopf:
self.menues[knopf]._schritt(1)
return "break"