Die Runde vom 07.09.2026 hat neben den zwei echten Fehlern eine Reihe Wiederholungen und stumme Stellen gemeldet. Abgearbeitet in Etappen, jede fuer sich pruefbar - nach jeder liefen beide Pruefketten gruen. * Menueleiste: der Wechsel von einem Titel zum anderen brauchte ZWEI Klicks. Der grab_set der offenen Liste schluckt den ersten, die <Button-1>-Bindung des Titels feuert gar nicht. Das native tk.Menu, das diese Leiste ersetzt hat, wechselte schon beim blossen Ueberfahren - zwei Klicks waren also eine Verschlechterung gegenueber dem, was vorher da war. Die Klappliste wird ausserdem jetzt wie jede andere Position ueber theme.auf_bildschirm() begrenzt; am unteren Fensterrand lief sie vorher hinaus. Neu: pruef_menueleiste.py misst beides ueber echte Klickereignisse. * %LOCALAPPDATA%/ravokk wurde an drei Stellen einzeln ausgerechnet. Jetzt fragen alle gemeinsam.standard_ordner(). Laufen die je auseinander, fuehren die beiden Programme zwei Nummernbuecher - und das faellt erst auf, wenn eine Rechnungsnummer zum zweiten Mal vergeben ist (§ 14 Abs. 4 Nr. 4 UStG). * pruefe() stand 35-mal im Quelltext, in sechs Fassungen -> pruefhelfer.py. Die zwei Ausreisser sind mitgezogen: pruef_bilder.py schrieb ein eigenes Format, pruef_storno_verrechnet.py zaehlte in einer Zahl und konnte am Ende nicht sagen, WAS fehlschlug. * dlg_darstellung() und die drei Handgriffe des Umschaltens standen in beiden app.py fast wortgleich -> ThemeMixin in theme.py. Beide trugen inzwischen denselben langen Kommentar zu demselben Fehler; das war der Beweis, dass es eine Sache ist. Programmspezifisch bleiben der Hinweistext im Dialog (R31) und die Frage, ob die Einstellungen ausdruecklich gespeichert werden muessen. * pruef_gemeinsam_automatisch.py sagte im Text "genau ein Buch" und prueft ">= 1" mit einem any() - eine Pruefung, die nicht rot werden kann. Jetzt == 1, und der Dateiname wird mitgeprueft. Dazu neu: die neun Dateien, die im Kopf zusagen, sie laegen in beiden Programmen gleich, werden byteweise verglichen. Bei hinweis.py stimmte die Zusage seit dem 06.09. nicht mehr - gleicher Inhalt, CRLF gegen LF. * .gitattributes, damit die Zeilenenden nicht von der Maschine abhaengen. Ohne das meldet genau diese Pruefung nach einem frischen Checkout einen Unterschied, den es im Repository gar nicht gibt. * Sechs stumme "except OSError" sagen jetzt, warum sie schweigen duerfen, und einer meldet statt zu schweigen: schlaegt die Uebernahme einer alten config.json fehl, faengt das Programm ohne Firmendaten, Katalog und Zaehler neu an - und der Erfolgsfall schrieb eine Zeile, der Fehlerfall nicht. Die 24 stummen tk.TclError bleiben: dort wird ein Widget angefasst, das gerade zerstoert wurde, und eine Meldung waere Rauschen. * Tote Parameter und Variablen aus dem Umbau des Vortags: symbol_setzen(dunkel=), kachel(grund=, radius=), mit_trennlinie, sechs x/y-Berechnungen, die von theme.mittig() sofort ueberschrieben wurden, vier lokale "import ctypes". 39 Pruefstaende, 0 rot. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V4t48uxDok5rJXhC9bX1Ax
184 lines
6.7 KiB
Python
184 lines
6.7 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Pruefstand: die eigene Menueleiste - Wechsel mit EINEM Klick, und am Rand.
|
|
|
|
WARUM ES DIESE LEISTE GIBT
|
|
--------------------------
|
|
`tk.Menu` ist unter Windows ein Menue des Betriebssystems. Es nimmt Farben
|
|
entgegen und zeichnet trotzdem im Systemgrau - ein weisser Balken ueber einem
|
|
dunklen Fenster. Sein Befund am 07.09.2026: *"nur die einstellunden hilfe
|
|
leiste die ist noch hell"*. Deshalb ist die Leiste aus gewoehnlichen Labels
|
|
gebaut.
|
|
|
|
WAS HIER GEPRUEFT WIRD
|
|
|
|
1. Ein Klick auf einen Titel klappt seine Liste auf, ein zweiter auf denselben
|
|
Titel klappt sie wieder zu.
|
|
|
|
2. Steht eine Liste offen und man klickt auf einen ANDEREN Titel, klappt
|
|
dessen Liste auf - mit EINEM Klick.
|
|
|
|
Das ging bis zum 07.09.2026 nicht. Solange eine Liste offen ist, haelt ihr
|
|
`grab_set` alle Mausklicks der Anwendung fest; die `<Button-1>`-Bindung des
|
|
Menuetitels feuert dann gar nicht. Der Klick landete beim Zuklappen und war
|
|
damit verbraucht - man musste ein zweites Mal klicken, um von "Datei" zu
|
|
"Hilfe" zu kommen. Das native tk.Menu wechselte schon beim blossen
|
|
Ueberfahren; zwei Klicks waren also eine Verschlechterung gegenueber dem,
|
|
was ersetzt wurde.
|
|
|
|
Geprueft wird das ueber ein echtes Klickereignis auf der offenen Liste, mit
|
|
den Bildschirmkoordinaten des anderen Titels - also genau der Weg, den der
|
|
grab_set erzwingt. Ein direkter Aufruf von aufklappen() wuerde am Problem
|
|
vorbeipruefen.
|
|
|
|
3. Die Liste bleibt auf dem Bildschirm. Steht das Fenster am unteren Rand,
|
|
lief sie vorher darueber hinaus und war nicht mehr zu treffen: die Position
|
|
wurde ungeprueft gesetzt, waehrend jede andere neue Position im Programm
|
|
ueber theme.auf_bildschirm() laeuft.
|
|
|
|
Diese Datei liegt in BEIDEN Programmen gleich. Wer sie aendert, kopiert sie
|
|
hinueber - sonst steht in zwei Programmen zweierlei.
|
|
"""
|
|
import os
|
|
import sys
|
|
import time
|
|
import tkinter as tk
|
|
|
|
QUELLE = os.path.dirname(os.path.abspath(__file__))
|
|
sys.path.insert(0, QUELLE)
|
|
|
|
import menueleiste # noqa: E402
|
|
import theme # noqa: E402
|
|
from pruefhelfer import fehler, pruefe # noqa: E402
|
|
|
|
|
|
class Fenster(theme.ThemeMixin, tk.Tk):
|
|
"""Ein nacktes Fenster mit Farben - mehr braucht die Leiste nicht."""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.cfg = {} # theme.modus() will etwas mit .get()
|
|
self._apply_theme()
|
|
self.title("Pruefstand Menueleiste")
|
|
self.geometry("560x320" + theme.pruefplatz())
|
|
|
|
|
|
f = Fenster()
|
|
gerufen = []
|
|
f.menue = menueleiste.Menueleiste(f, {
|
|
"Datei": [("Neu", lambda: gerufen.append("neu")), ("-", None),
|
|
("Beenden", lambda: gerufen.append("beenden"))],
|
|
"Bearbeiten": [("Rueckgaengig", lambda: gerufen.append("rueck"))],
|
|
"Hilfe": [("Info", lambda: gerufen.append("info"))],
|
|
})
|
|
|
|
|
|
def pumpen(sekunden=0.4):
|
|
ende = time.time() + sekunden
|
|
while time.time() < ende:
|
|
f.update()
|
|
time.sleep(0.02)
|
|
|
|
|
|
pumpen(1.0)
|
|
|
|
titel = {str(w.cget("text")): w for w in f.menue.winfo_children()
|
|
if isinstance(w, tk.Label)}
|
|
pruefe(set(titel) == {"Datei", "Bearbeiten", "Hilfe"},
|
|
f"alle drei Titel stehen in der Leiste ({sorted(titel)})")
|
|
|
|
|
|
def klick_auf_titel(name):
|
|
"""Auf einen Menuetitel klicken - so, wie es die Maus tut.
|
|
|
|
Steht schon eine Liste offen, MUSS das Ereignis an sie gehen: ihr grab_set
|
|
faengt jeden Klick der Anwendung ab. Genau das ist der Fall, um den es
|
|
unter Punkt 2 geht.
|
|
"""
|
|
w = titel[name]
|
|
x = w.winfo_rootx() + w.winfo_width() // 2
|
|
y = w.winfo_rooty() + w.winfo_height() // 2
|
|
ziel = f.menue._offen or w
|
|
if ziel is w:
|
|
# Achtung: Tk nennt die Bildschirmkoordinaten beim Erzeugen -rootx
|
|
# und -rooty; im Ereignis heissen sie dann x_root und y_root.
|
|
w.event_generate("<Button-1>", x=w.winfo_width() // 2,
|
|
y=w.winfo_height() // 2, rootx=x, rooty=y)
|
|
else:
|
|
ziel.event_generate("<Button-1>", x=-1, y=-1, rootx=x, rooty=y)
|
|
pumpen(0.4)
|
|
|
|
|
|
def offener_titel():
|
|
"""Welcher Titel gerade offen ist - oder None."""
|
|
anker = f.menue._anker
|
|
if anker is None or not f.menue._offen:
|
|
return None
|
|
return next((t for t, w in titel.items() if w is anker), "?")
|
|
|
|
|
|
# ---- 1. Aufklappen und wieder zuklappen ---------------------------------
|
|
klick_auf_titel("Datei")
|
|
pruefe(offener_titel() == "Datei", f"'Datei' klappt auf ({offener_titel()})")
|
|
|
|
klick_auf_titel("Datei")
|
|
pruefe(offener_titel() is None,
|
|
f"und derselbe Titel klappt wieder zu ({offener_titel()})")
|
|
|
|
# ---- 2. Der Wechsel: EIN Klick ------------------------------------------
|
|
klick_auf_titel("Datei")
|
|
pruefe(offener_titel() == "Datei", "'Datei' steht offen")
|
|
|
|
klick_auf_titel("Hilfe")
|
|
pruefe(offener_titel() == "Hilfe",
|
|
f"ein Klick auf 'Hilfe' wechselt hinueber ({offener_titel()})")
|
|
|
|
klick_auf_titel("Bearbeiten")
|
|
pruefe(offener_titel() == "Bearbeiten",
|
|
f"und weiter auf 'Bearbeiten' ({offener_titel()})")
|
|
|
|
# Ein Klick irgendwo neben die Leiste schliesst - und oeffnet nichts.
|
|
pop = f.menue._offen
|
|
pop.event_generate("<Button-1>", x=-1, y=-1,
|
|
rootx=f.winfo_rootx() + 300,
|
|
rooty=f.winfo_rooty() + 250)
|
|
pumpen(0.4)
|
|
pruefe(offener_titel() is None,
|
|
f"ein Klick daneben schliesst und oeffnet nichts ({offener_titel()})")
|
|
|
|
# ---- 3. Ein Eintrag fuehrt seinen Befehl aus ----------------------------
|
|
klick_auf_titel("Hilfe")
|
|
eintrag = None
|
|
for kind in f.menue._offen.winfo_children():
|
|
for enkel in kind.winfo_children():
|
|
if isinstance(enkel, tk.Label) and str(enkel.cget("text")) == "Info":
|
|
eintrag = enkel
|
|
eintrag.event_generate("<Button-1>", x=2, y=2)
|
|
pumpen(0.4)
|
|
pruefe(gerufen == ["info"], f"der Eintrag 'Info' fuehrt seinen Befehl aus ({gerufen})")
|
|
pruefe(offener_titel() is None, "und die Liste ist danach zu")
|
|
|
|
# ---- 4. Am unteren Bildschirmrand bleibt die Liste sichtbar -------------
|
|
# Das Fenster so weit nach unten schieben, dass unter der Leiste kein Platz
|
|
# mehr fuer die Klappliste ist.
|
|
links, oben = theme.auf_bildschirm(f.winfo_rootx(), f.winfo_rooty())
|
|
unten = oben + f.winfo_screenheight()
|
|
f.geometry(f"560x320+{links}+{unten - 120}")
|
|
pumpen(0.6)
|
|
|
|
klick_auf_titel("Datei")
|
|
pop = f.menue._offen
|
|
pruefe(pop is not None, "die Liste geht auch am unteren Rand auf")
|
|
if pop is not None:
|
|
x, y = pop.winfo_rootx(), pop.winfo_rooty()
|
|
hoehe, breite = pop.winfo_height(), pop.winfo_width()
|
|
grenze_x, grenze_y = theme.auf_bildschirm(x, y, breite, hoehe)
|
|
pruefe((x, y) == (grenze_x, grenze_y),
|
|
f"und steht vollstaendig auf dem Bildschirm "
|
|
f"(Liste {x},{y} bis {x + breite},{y + hoehe})")
|
|
|
|
f.destroy()
|
|
|
|
if fehler:
|
|
print(f"ROT | {len(fehler)} Punkt(e) nicht erfuellt")
|
|
sys.exit(1)
|
|
print("GRUEN | Die Menueleiste wechselt mit einem Klick und bleibt im Bild")
|