rechnungstool/wasserzeichen.py
TheMockTv 73042b771c Logo in der Statuszeile, Bau an einer Stelle, Startprobe fuer die EXE
Logo
  Das Programmlogo steht jetzt klein neben "powered by ravokk" - dort sitzt
  die Kennung des Werkzeugs, nicht die des Kunden. Eine eigene Kopfzeile hat
  dieses Programm nicht; oben sind die Reiter.

bauen.py
  Der Weg von der Quelle zur Setup-Datei war eine Folge von Befehlen, die
  jemand von Hand eintippen musste. Wer einen Schritt vergisst, liefert eine
  Setup-Datei mit einer alten EXE aus - und sieht es ihr nicht an. Genau das
  ist heute passiert. Jetzt ist es ein Befehl, die Nummer kommt aus
  version.py, und am Ende stehen die SHA256-Summen fuer den Release-Text.

pruef_start_exe.py
  Prueft, was kein anderer Pruefstand prueft: ob die GEBAUTE EXE ein
  sichtbares Fenster zeigt. Alle uebrigen fahren das Programm aus der Quelle,
  dort trat der Fehler nie auf. "Der Prozess laeuft" beweist dabei nichts -
  er lief ja, waehrend nichts zu sehen war.

Aufgeraeumt
  "Rechnung Campinghof.exe" ist weg; gebaute Dateien gehoeren ins Release,
  nicht ins Repo. Die .spec-Dateien dagegen schon: ohne sie kann niemand die
  EXE nachbauen, die der Updater ausliefert.

Fassung 1.7.0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V4t48uxDok5rJXhC9bX1Ax
2026-09-06 20:34:59 +02:00

106 lines
3.8 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_setzen(fenster, melden=print):
"""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.
"""
p = 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 hole(groesse=240, deckkraft=0.06, master=None, melden=print):
"""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))
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)
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):
"""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)