Eigene Kennungen ausblenden, Win95-Menüs, verschiebbares Hauptfenster bei offenem Dialog u. a.
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:
1 parent
2b2d5e8580
commit
dcb1d0378c
15 files changed
+1217
-187
No files matched your search
@@ -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"
|
||||
Reference in new issue
Block a user