Seine Ansage "da gehen noch die popups auf dem video monitor auf" "einfach so programmieren das die popup auf dem fenster mittig vom main fenster auf gehen" Was falsch war An sechs Stellen stand `max(0, x)`. Das sollte verhindern, dass ein Dialog ausserhalb des Bildschirms aufgeht - klemmt aber jede NEGATIVE Koordinate auf null. Bei ihm steht der zweite Monitor links (x = -1920): jedes Popup sprang damit zurueck auf den Hauptbildschirm, obwohl das Programm nebenan lief. Dazu kamen feste Versaetze wie "+120+140", die mit der Fenstergroesse nichts zu tun hatten. theme.mittig(dialog, ueber) Eine Stelle fuer alle Dialoge: mittig ueber dem Elternfenster, etwas oberhalb der Mitte (ein Drittel statt der Haelfte), damit der Blick dort ist und die Mitte des Formulars frei bleibt. Das ist zugleich die Antwort auf die Monitorfrage: steht das Programm auf dem zweiten Bildschirm, geht der Dialog dort auf - ohne dass irgendwo ein Monitor gesucht werden muss. Die Sonderbehandlung im Testlauf konnte deshalb entfallen; geblieben ist dort nur der Verzicht auf grab_set. theme.auf_bildschirm(x, y, breite, hoehe) Begrenzt eine Position auf den GESAMTEN Desktop ueber alle Monitore (SM_*VIRTUALSCREEN) statt auf den Hauptmonitor. Das war die eigentliche Absicht hinter dem alten max(0, ...). Gemessen statt geglaubt: Dialogmitte und Fenstermitte liegen 8 Pixel auseinander (Fensterrahmen), der Dialog liegt innerhalb des Fensters. Steht als Pruefung in pruef_darstellung.py. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V4t48uxDok5rJXhC9bX1Ax
218 lines
7.2 KiB
Python
218 lines
7.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Pruefstand: hell, dunkel, automatisch - und nichts geht dabei verloren.
|
|
|
|
Sein Wortlaut vom 07.09.2026:
|
|
|
|
"waere nun noch gut das mann in den einstellungen einstellen kann hell
|
|
oder dunkel oder automatik das fehlt sollte eigendlich drin sein"
|
|
|
|
GEPRUEFT WIRD DREIERLEI
|
|
|
|
1. Die Einstellung wirkt. "hell" ist hell, "dunkel" ist dunkel, "auto" folgt
|
|
Windows.
|
|
|
|
2. Beim Umschalten bleibt jede Eingabe stehen. Das ist der Punkt, an dem es
|
|
beim Bauen schiefging: die Mengen der Leistungszeilen hatte ich einzeln
|
|
gesichert, Kundenname und Rechnungsnummer nicht - die _build_-Methoden
|
|
legen ihre Variablen selbst an, beim Neubau standen also leere da. Wer
|
|
waehrend einer halb getippten Rechnung auf dunkel stellt, darf sie nicht
|
|
verlieren.
|
|
|
|
3. NACH dem Wechsel traegt kein Widget mehr eine Farbe der anderen Palette.
|
|
Das ist die eigentliche Falle beim Umfaerben: ein einzelnes vergessenes
|
|
Label leuchtet hell im dunklen Fenster, und niemand findet die Stelle
|
|
wieder. Deshalb wird der ganze Widget-Baum abgegangen und mit den Farben
|
|
der jeweils ANDEREN Palette verglichen.
|
|
"""
|
|
import json
|
|
import os
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
import time
|
|
import tkinter as tk
|
|
|
|
QUELLE = os.path.dirname(os.path.abspath(__file__))
|
|
sys.path.insert(0, QUELLE)
|
|
|
|
fehler = []
|
|
|
|
|
|
def pruefe(bedingung, text):
|
|
zeile = (" OK | " if bedingung else "FEHLER | ") + text
|
|
kod = sys.stdout.encoding or "utf-8"
|
|
print(zeile.encode(kod, "replace").decode(kod))
|
|
if not bedingung:
|
|
fehler.append(text)
|
|
|
|
|
|
os.chdir(QUELLE)
|
|
os.environ["CAMPINGHOF_TEST"] = "1"
|
|
|
|
from tkinter import messagebox # noqa: E402
|
|
|
|
messagebox.askyesno = lambda *a, **k: True
|
|
for _n in ("showinfo", "showwarning", "showerror"):
|
|
setattr(messagebox, _n, lambda *a, **k: None)
|
|
|
|
import config # noqa: E402
|
|
|
|
# Auf einer Kopie arbeiten - die echte Konfiguration wird nicht angefasst.
|
|
tmp = tempfile.mkdtemp(prefix="darstellung_")
|
|
shutil.copy(os.path.join(QUELLE, "config.json"), os.path.join(tmp, "config.json"))
|
|
config.CONFIG_PATH = os.path.join(tmp, "config.json")
|
|
roh = json.load(open(config.CONFIG_PATH, encoding="utf-8"))
|
|
roh["nummer_eingerichtet"] = True
|
|
roh.pop("darstellung", None)
|
|
json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"),
|
|
ensure_ascii=False, indent=2)
|
|
|
|
import app as A # noqa: E402
|
|
import theme # noqa: E402
|
|
|
|
f = A.RechnungsApp()
|
|
|
|
|
|
def pumpen(sekunden=1.0):
|
|
ende = time.time() + sekunden
|
|
while time.time() < ende:
|
|
f.update()
|
|
time.sleep(0.02)
|
|
|
|
|
|
pumpen(2.0)
|
|
|
|
# ---- Ohne Einstellung: automatisch --------------------------------------
|
|
pruefe(theme.modus(f.cfg) == "auto",
|
|
f"ohne Eintrag gilt 'auto' ({theme.modus(f.cfg)!r})")
|
|
pruefe(f._dark_mode == theme.windows_dark_mode(),
|
|
"und folgt damit dem Windows-Theme")
|
|
|
|
# ---- Eingaben, die den Wechsel ueberleben muessen ------------------------
|
|
f.k_vorname.set("Erika")
|
|
f.k_name.set("Mustermann")
|
|
f.k_strasse.set("Musterweg 12")
|
|
f.k_plzort.set("04416 Markkleeberg")
|
|
f.r_nummer.set("2026-099")
|
|
f.r_anreise.set("01.09.2026")
|
|
erste = sorted(f.anzahl_vars)[0]
|
|
f.anzahl_vars[erste].set("3")
|
|
pumpen(0.5)
|
|
|
|
|
|
def farben(dunkel):
|
|
"""Die Palette der einen oder anderen Darstellung, als Menge."""
|
|
merker = f._dark_mode
|
|
f.cfg["darstellung"] = "dunkel" if dunkel else "hell"
|
|
f._apply_theme()
|
|
satz = {getattr(f, n).lower() for n in dir(f)
|
|
if n.startswith("col_") and isinstance(getattr(f, n), str)}
|
|
f._dark_mode = merker
|
|
return satz
|
|
|
|
|
|
HELL = farben(False)
|
|
DUNKEL = farben(True)
|
|
# Was in BEIDEN Paletten vorkommt, taugt nicht als Beweis.
|
|
NUR_HELL = HELL - DUNKEL
|
|
NUR_DUNKEL = DUNKEL - HELL
|
|
|
|
|
|
def fremdfarben(verboten):
|
|
"""Alle Widgets, die eine Farbe der falschen Palette tragen."""
|
|
treffer = []
|
|
|
|
def durchgehen(w):
|
|
for kind in w.winfo_children():
|
|
for eigenschaft in ("bg", "background", "fg", "foreground"):
|
|
try:
|
|
wert = str(kind.cget(eigenschaft)).lower()
|
|
except tk.TclError:
|
|
continue
|
|
if wert in verboten:
|
|
treffer.append((str(kind), eigenschaft, wert))
|
|
durchgehen(kind)
|
|
|
|
durchgehen(f)
|
|
return treffer
|
|
|
|
|
|
# ---- Die drei Modi ------------------------------------------------------
|
|
for wunsch, soll_dunkel in (("hell", False), ("dunkel", True)):
|
|
f.cfg["darstellung"] = wunsch
|
|
config.save_config(f.cfg)
|
|
f._oberflaeche_neu()
|
|
pumpen(0.8)
|
|
|
|
pruefe(f._dark_mode == soll_dunkel,
|
|
f"'{wunsch}' zeichnet {'dunkel' if soll_dunkel else 'hell'} "
|
|
f"(Hintergrund {f.col_bg})")
|
|
|
|
# Nichts darf verlorengehen.
|
|
pruefe(f.k_name.get() == "Mustermann",
|
|
f"nach dem Wechsel steht der Kundenname noch da ({f.k_name.get()!r})")
|
|
pruefe(f.r_nummer.get() == "2026-099",
|
|
f"und die Rechnungsnummer ({f.r_nummer.get()!r})")
|
|
pruefe(f.k_plzort.get() == "04416 Markkleeberg",
|
|
f"und die Anschrift ({f.k_plzort.get()!r})")
|
|
pruefe(f.r_anreise.get() == "01.09.2026",
|
|
f"und das Anreisedatum ({f.r_anreise.get()!r})")
|
|
pruefe(f.anzahl_vars.get(erste) is not None
|
|
and f.anzahl_vars[erste].get() == "3",
|
|
"und die eingetragene Menge in der Leistungszeile")
|
|
|
|
# Kein Rest der anderen Palette.
|
|
reste = fremdfarben(NUR_DUNKEL if not soll_dunkel else NUR_HELL)
|
|
pruefe(not reste,
|
|
f"in '{wunsch}' ist keine Farbe der anderen Darstellung "
|
|
f"uebrig ({reste[:3]})")
|
|
|
|
# ---- Dialoge gehen mittig ueber dem Programmfenster auf ------------------
|
|
# Seine Ansage: "einfach so programmieren das die popup auf dem fenster mittig
|
|
# vom main fenster auf gehen". Das ist zugleich die Antwort auf die
|
|
# Monitorfrage - steht das Programm auf dem zweiten Bildschirm, geht der
|
|
# Dialog dort auf. Vorher klemmte ein max(0, x) jede negative Koordinate auf
|
|
# null und riss die Fenster auf den Hauptmonitor zurueck.
|
|
d = f.dlg_darstellung()
|
|
pumpen(1.2)
|
|
d.update_idletasks()
|
|
mitte_dialog = d.winfo_rootx() + d.winfo_width() // 2
|
|
mitte_fenster = f.winfo_rootx() + f.winfo_width() // 2
|
|
pruefe(abs(mitte_dialog - mitte_fenster) <= 30,
|
|
f"der Dialog steht mittig ueber dem Fenster "
|
|
f"({mitte_dialog} vs. {mitte_fenster})")
|
|
pruefe(d.winfo_rootx() >= f.winfo_rootx() - 40
|
|
and d.winfo_rootx() + d.winfo_width() <= f.winfo_rootx() + f.winfo_width() + 40,
|
|
"und liegt waagerecht innerhalb des Fensters")
|
|
pruefe(d.winfo_rooty() >= f.winfo_rooty() - 40,
|
|
"und nicht oberhalb davon")
|
|
d.destroy()
|
|
pumpen(0.3)
|
|
|
|
# ---- Zurueck auf automatisch --------------------------------------------
|
|
f.cfg["darstellung"] = "auto"
|
|
config.save_config(f.cfg)
|
|
f._oberflaeche_neu()
|
|
pumpen(0.8)
|
|
pruefe(f._dark_mode == theme.windows_dark_mode(),
|
|
"'auto' folgt wieder dem Windows-Theme")
|
|
|
|
# ---- Und die Einstellung ueberlebt den Neustart -------------------------
|
|
f.cfg["darstellung"] = "hell"
|
|
config.save_config(f.cfg)
|
|
f.destroy()
|
|
f = A.RechnungsApp()
|
|
pumpen(2.0)
|
|
pruefe(f._dark_mode is False,
|
|
"die Einstellung steht beim naechsten Start wieder da")
|
|
|
|
try:
|
|
f.destroy()
|
|
except Exception: # noqa: BLE001
|
|
pass
|
|
shutil.rmtree(tmp, ignore_errors=True)
|
|
|
|
if fehler:
|
|
print(f"ROT | {len(fehler)} Punkt(e) nicht erfuellt")
|
|
sys.exit(1)
|
|
print("GRUEN | Hell, dunkel und automatisch wirken - ohne Datenverlust")
|