Seine Ansage "waere nun noch gut das mann in den einstellungen einstellen kann hell oder dunkel oder automatik das fehlt sollte eigendlich drin sein" Er hatte recht: beide Farbpaletten lagen laengst in theme.py, das Programm folgte aber starr dem Windows-Theme. Jetzt steht die Wahl unter Einstellungen -> Darstellung und wirkt SOFORT, nicht erst beim Neustart. Umschalten heisst neu bauen Die Farben stehen an ueber sechzig Stellen fest in den Widgets. Sie einzeln nachzuziehen hiesse, ein Verzeichnis aller eingefaerbten Widgets zu fuehren - und wer beim naechsten neuen Widget vergisst, es einzutragen, hat einen hellen Fleck im dunklen Fenster, den niemand mehr findet. Neu bauen kann nichts vergessen. ⚠️ Dabei gingen im ersten Versuch Kundenname und Rechnungsnummer verloren: die _build_-Methoden legen ihre StringVars SELBST an, nach dem Neubau standen leere da. Gesichert werden jetzt ALLE Tk-Variablen des Fensters, nicht eine gepflegte Auswahl - wer spaeter ein Feld hinzufuegt, muss nichts eintragen. Titelleiste, Symbol und Logo gehen mit Die Titelleiste gehoert Windows; sie folgt ueber DWMWA_USE_IMMERSIVE_DARK_MODE. _dark_titlebar konnte bisher nur DUNKEL und zeigt jetzt in beide Richtungen - vorher behielt man beim Wechsel auf hell eine schwarze Leiste ueber einem hellen Fenster. Sein Befund: "die svg fuer das dunkle heller machen weil die geht nun unter" und "icon musst wenn es dunkel ist mit aendern auf das hellere". Gemessen sind es zwei Farben: Petrol #004860 und ein fast schwarzes #181818 - letzteres liegt genau auf der Kachelfarbe #15171b. Umgestellt wird nur die HELLIGKEIT (L -> 1-L, gedeckelt bei 0.78), Farbton und Saettigung bleiben: sonst waere es nicht mehr sein Logo. Betrifft Wasserzeichen, Kopflogo und das Fenstersymbol; die aufgehellte ICO entsteht beim Bauen. menueleiste.py Die selbstgebaute Menueleiste ist jetzt eine eigene Datei und liegt in beiden Programmen gleich - das Steuerrechnungstool hatte noch das native tk.Menu, das sich unter Windows nicht einfaerben laesst. pruef_darstellung.py Prueft die drei Modi, dass beim Umschalten NICHTS verlorengeht, und dass danach kein Widget mehr eine Farbe der anderen Palette traegt - genau der helle Fleck, den man sonst nie wiederfindet. Nebenbei: die beiden Dauertimer melden beim Beenden kein "invalid command name ..._api_pumpe" mehr. Ein Log, in dem beim normalen Beenden immer zwei Fehler stehen, liest irgendwann niemand mehr. Fassung 1.8.0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V4t48uxDok5rJXhC9bX1Ax
183 lines
7 KiB
Python
183 lines
7 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Das Programmlogo - als Fenstersymbol und als blasses Wasserzeichen.
|
|
|
|
WOHER DAS BILD KOMMT
|
|
--------------------
|
|
Die Quelle ist die SVG aus dem Entwurf (`assets/programm_logo.svg`). Sie wird
|
|
BEIM BAUEN einmal in 512 Pixel gerendert (`assets/programm_logo.png`) und in
|
|
eine `programm.ico` fuer Windows umgesetzt. Hier zur Laufzeit wird nur noch
|
|
skaliert.
|
|
|
|
Warum nicht direkt aus der SVG zeichnen? Weil dafuer `svglib` und `lxml` mit in
|
|
die EXE muessten - und bis 512 Pixel ist das Ergebnis pixelgenau dasselbe. Wird
|
|
das Wasserzeichen je groesser gebraucht, gehoert das umgestellt.
|
|
|
|
Warum ueberhaupt eine ICO? Weil Windows das Symbol einer EXE als Ressource in
|
|
der Datei erwartet, und dieses Format ist ICO. Das gilt fuer jede Sprache, nicht
|
|
nur fuer Python - eine SVG kann Windows dort nicht verwenden.
|
|
|
|
Diese Datei liegt in BEIDEN Programmen gleich. Wer sie aendert, kopiert sie
|
|
hinueber.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import tkinter as tk
|
|
|
|
_lager = {} # (fenster, groesse, deckkraft) -> PhotoImage
|
|
|
|
|
|
def _ordner():
|
|
"""Alle Orte, an denen die Bilder liegen koennen - Quelle wie EXE."""
|
|
hier = os.path.dirname(os.path.abspath(__file__))
|
|
aus_exe = getattr(sys, "_MEIPASS", "")
|
|
neben_exe = os.path.dirname(os.path.abspath(sys.executable)) \
|
|
if getattr(sys, "frozen", False) else ""
|
|
return [p for p in (aus_exe, neben_exe, hier) if p]
|
|
|
|
|
|
def pfad(name):
|
|
"""Voller Pfad zu einer Datei in assets/ - oder ''."""
|
|
for basis in _ordner():
|
|
p = os.path.join(basis, "assets", name)
|
|
if os.path.exists(p):
|
|
return p
|
|
return ""
|
|
|
|
|
|
def symbol_datei_dunkel():
|
|
"""Pfad zur aufgehellten ICO - wird angelegt, wenn sie fehlt.
|
|
|
|
Sein Befund am 07.09.2026: *"icon musst wenn es dunkel ist mit ändern auf
|
|
das hellere geht sonst mit unter"*. Die dunkle Haelfte des Zeichens liegt
|
|
genau auf der Farbe der dunklen Titelleiste.
|
|
|
|
`iconbitmap` braucht eine DATEI - ein Bild im Speicher genuegt nicht.
|
|
Deshalb wird sie einmal neben die normale gelegt. Gelingt das nicht (kein
|
|
Schreibrecht im Programmordner), landet sie im Temp-Ordner; und wenn auch
|
|
das nicht geht, bleibt es beim normalen Symbol.
|
|
"""
|
|
fertig = pfad("programm_dunkel.ico")
|
|
if fertig:
|
|
return fertig
|
|
quelle = pfad("programm_logo.png")
|
|
if not quelle:
|
|
return ""
|
|
ziel = os.path.join(os.path.dirname(quelle), "programm_dunkel.ico")
|
|
try:
|
|
from PIL import Image
|
|
bild = _fuer_dunkel(Image.open(quelle).convert("RGBA"))
|
|
groessen = [(g, g) for g in (16, 24, 32, 48, 64, 128, 256)]
|
|
try:
|
|
bild.save(ziel, sizes=groessen)
|
|
except OSError:
|
|
import tempfile
|
|
ziel = os.path.join(tempfile.gettempdir(), "programm_dunkel.ico")
|
|
bild.save(ziel, sizes=groessen)
|
|
return ziel
|
|
except Exception: # noqa: BLE001 - ohne helles Symbol laeuft es weiter
|
|
return ""
|
|
|
|
|
|
def symbol_setzen(fenster, melden=print, dunkel=False):
|
|
"""Das Programmsymbol auf ein Fenster legen. True bei Erfolg.
|
|
|
|
Die EXE traegt es ohnehin - das Fenster aber nicht von selbst. Ohne das
|
|
steht in der Titelleiste das nackte Tk-Symbol, obwohl die Datei ein eigenes
|
|
hat.
|
|
|
|
In der dunklen Darstellung wird die aufgehellte Fassung genommen, sonst
|
|
verschwindet die dunkle Haelfte des Zeichens in der dunklen Titelleiste.
|
|
"""
|
|
p = (symbol_datei_dunkel() if dunkel else "") or pfad("programm.ico")
|
|
if not p:
|
|
melden("[symbol] assets/programm.ico nicht gefunden")
|
|
return False
|
|
try:
|
|
fenster.iconbitmap(default=p)
|
|
return True
|
|
except Exception as e: # noqa: BLE001 - ohne Symbol laeuft es weiter
|
|
melden(f"[symbol] konnte nicht gesetzt werden: {e}")
|
|
return False
|
|
|
|
|
|
def _fuer_dunkel(bild):
|
|
"""Das Logo fuer die dunkle Darstellung aufhellen.
|
|
|
|
Sein Befund am 07.09.2026: *"wir muessen mal schauen das wir die svg fuer
|
|
das dunkle heller machen weil die geht nun unter"*.
|
|
|
|
Gemessen sind es zwei Farben: das Petrol #004860 (Helligkeit 53) und ein
|
|
fast schwarzes #181818 (Helligkeit 24). Der zweite Wert liegt genau auf der
|
|
Kachelfarbe #15171b - deshalb verschwindet die Haelfte des Zeichens.
|
|
|
|
Umgestellt wird nur die HELLIGKEIT, Farbton und Saettigung bleiben: sonst
|
|
waere es nicht mehr sein Logo, sondern ein anderes. Aus dunkel wird hell,
|
|
aus hell dunkel (L -> 1-L), und weil eine reine Umkehr das kraeftige Petrol
|
|
fast weiss werden liesse, wird sie auf 0.78 gedeckelt.
|
|
|
|
Die Kanten (halbdurchsichtige Punkte aus dem Skalieren) gehen denselben Weg
|
|
- deshalb Pixel fuer Pixel und nicht ein Farbtausch mit Toleranz.
|
|
"""
|
|
import colorsys
|
|
bild = bild.convert("RGBA")
|
|
punkte = list(bild.getdata())
|
|
neu = []
|
|
zwischen = {}
|
|
for r, g, b, a in punkte:
|
|
if a == 0:
|
|
neu.append((r, g, b, a))
|
|
continue
|
|
schluessel = (r, g, b)
|
|
if schluessel not in zwischen:
|
|
h, l, s2 = colorsys.rgb_to_hls(r / 255, g / 255, b / 255)
|
|
hell = min(1.0 - l, 0.78)
|
|
r2, g2, b2 = colorsys.hls_to_rgb(h, hell, s2)
|
|
zwischen[schluessel] = (int(r2 * 255), int(g2 * 255), int(b2 * 255))
|
|
neu.append(zwischen[schluessel] + (a,))
|
|
bild.putdata(neu)
|
|
return bild
|
|
|
|
|
|
def hole(groesse=240, deckkraft=0.06, master=None, melden=print, dunkel=False):
|
|
"""Das Logo blass als PhotoImage - oder None.
|
|
|
|
`deckkraft` 0..1, klein halten. Ein Wasserzeichen, das man LIEST, stoert
|
|
beim Arbeiten; eines, das man ahnt, gehoert zum Programm.
|
|
|
|
`master` MUSS mitgegeben werden, wenn mehr als ein Tk-Fenster laeuft:
|
|
sonst gehoert das Bild dem falschen Fenster und Tk meldet spaeter, es
|
|
kenne es nicht.
|
|
"""
|
|
fenster = master if master is not None else tk._default_root
|
|
if fenster is None:
|
|
return None
|
|
schluessel = (id(fenster), groesse, round(deckkraft, 3), bool(dunkel))
|
|
if schluessel in _lager:
|
|
return _lager[schluessel]
|
|
p = pfad("programm_logo.png")
|
|
if not p:
|
|
melden("[wasserzeichen] assets/programm_logo.png nicht gefunden")
|
|
return None
|
|
try:
|
|
from PIL import Image, ImageTk
|
|
bild = Image.open(p).convert("RGBA").resize((groesse, groesse), Image.LANCZOS)
|
|
if dunkel:
|
|
bild = _fuer_dunkel(bild)
|
|
bild.putalpha(bild.getchannel("A").point(lambda w: int(w * deckkraft)))
|
|
fertig = ImageTk.PhotoImage(bild, master=fenster)
|
|
except Exception as e: # noqa: BLE001 - ein Wasserzeichen darf nichts blockieren
|
|
melden(f"[wasserzeichen] nicht ladbar: {e}")
|
|
return None
|
|
_lager[schluessel] = fertig
|
|
return fertig
|
|
|
|
|
|
def kopf(hoehe=28, master=None, melden=print, dunkel=False):
|
|
"""Das Logo fuer die Kopfzeile - volle Deckkraft, kleine Hoehe.
|
|
|
|
Getrennt von hole(): dort geht es um ein blasses Wasserzeichen, hier um ein
|
|
Erkennungszeichen. Zwei Zwecke, zwei Funktionen - sonst haette eine davon
|
|
einen Schalter, den man beim Lesen nachschlagen muss.
|
|
"""
|
|
return hole(hoehe, 1.0, master=master, melden=melden, dunkel=dunkel)
|