rechnungstool/icons.py
TheMockTv c1e328c087 Kaesten verschiebbar wie bei OPNsense: Knopf, schieben, speichern
Seine Ansage: "rechnungsdaten oben links, darunter rechnung an kunde, den
tabellen kasten rechts - oder noch besser dnd einbauen" und dann genauer: "wie
bei opnsense hast so einen kleinen knopf, dann drueckst drauf, ab da kannst du
schieben, dann hast du einen save button, und dann ist die fest".

- Seine Anordnung ist die Vorgabe (flaeche.STANDARD).
- Verschieben muss man EINSCHALTEN. Ohne den Knopf sitzt alles fest, damit beim
  taeglichen Arbeiten nichts verrutscht. Im Modus stehen "Speichern" und
  "Verwerfen" daneben; erst Speichern schreibt in die config.
- Waehrend des Ziehens haengt ein Etikett am Zeiger und der gegriffene Kasten
  wird blass - vorher sah man nur das Ziel und es wirkte "statisch" (sein Fund).
- Getauscht statt frei abgelegt: freies Verschieben auf Pixel braucht Regeln
  fuer Ueberlappung, Groesse und leere Zellen, und am Ende steht das Fenster
  schief. Ein Tausch ist in einem Griff erledigt.
- Der freie Platz folgt dem Kasten mit der langen Liste. Feste Gewichte gingen
  nicht: die Liste wandert, und der Kundenkasten wurde unten abgeschnitten.
- Eine unbrauchbare Anordnung in der config faellt auf die Vorgabe zurueck.

Icons: die Material Symbols vom Hub (M:/projekte/material-symbols), einmal nach
PNG gewandelt und zur Laufzeit eingefaerbt (icons.py) - eine Datei je Icon,
nicht drei Fassungen fuer hell/dunkel/Akzent.

⚠️ Ein PhotoImage gehoert GENAU EINEM Tk-Fenster. Der Icon-Cache reichte Bilder
an das naechste weiter, sobald ein Pruefstand Journal und Rechnungstool
nacheinander faehrt: 'image "pyimage1" doesn't exist'. Das Fenster wird jetzt
mitgegeben, tk._default_root reicht dafuer nicht.

Neu: pruef_flaeche.py. Alle 15 Pruefstaende gruen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V4t48uxDok5rJXhC9bX1Ax
2026-09-05 19:53:42 +02:00

62 lines
2.5 KiB
Python

# -*- coding: utf-8 -*-
"""Icons aus dem Hub-Vorrat (Material Symbols), eingefaerbt zur Laufzeit.
Die SVGs liegen auf dem Hub (`M:/projekte/material-symbols`) und wurden einmal
nach PNG gewandelt - als weisse Silhouette mit Transparenz. Hier bekommen sie
ihre Farbe: dieselbe Datei taugt fuer die Akzentfarbe, fuer gedaempfte Schrift
und fuer das helle Thema, ohne dass jemand drei Fassungen pflegt.
Ohne Pillow oder ohne Datei gibt es einfach kein Bild zurueck - der Aufrufer
zeigt dann seinen Text. Ein fehlendes Icon darf kein Fenster verhindern.
"""
import os
import tkinter as tk
import config
# ⚠️ Ein PhotoImage gehoert zu GENAU EINEM Tk-Fenster. Wird ein zweites
# aufgemacht (ein Pruefstand faehrt Journal und Rechnungstool nacheinander),
# zeigt ein weitergereichtes Bild ins Leere: 'image "pyimage1" doesn't exist'.
# Deshalb steht das Fenster mit im Schluessel.
_zwischenlager = {} # (fenster, name, groesse, farbe) -> PhotoImage
def _pfad(name):
for basis in (config.BASE_DIR, config.RESOURCE_DIR,
os.path.dirname(os.path.abspath(__file__))):
p = os.path.join(basis, "assets", "icons", name + ".png")
if os.path.exists(p):
return p
return ""
def hole(name, groesse=18, farbe="#e8e8ea", master=None):
"""Icon als PhotoImage - oder None, wenn es nicht geht.
`master` ist das Widget, in dem das Bild landet. Es MUSS mitgegeben werden,
wenn mehr als ein Tk-Fenster laeuft: `tk._default_root` zeigt dann auf das
zuerst geoeffnete, und das Bild gehoert dem falschen Fenster.
"""
fenster = master if master is not None else tk._default_root
if fenster is None:
return None
schluessel = (id(fenster), name, groesse, farbe)
if schluessel in _zwischenlager:
return _zwischenlager[schluessel]
pfad = _pfad(name)
if not pfad:
print(f"[icons] nicht gefunden: {name}")
return None
try:
from PIL import Image, ImageTk
bild = Image.open(pfad).convert("RGBA")
bild = bild.resize((groesse, groesse), Image.LANCZOS)
# Die Silhouette steckt im Alphakanal - die Farbe kommt hier drueber.
flaeche = Image.new("RGBA", bild.size, farbe)
flaeche.putalpha(bild.getchannel("A"))
fertig = ImageTk.PhotoImage(flaeche, master=fenster)
except Exception as e: # noqa: BLE001 - ein Icon darf nichts blockieren
print(f"[icons] {name} nicht ladbar: {e}")
return None
_zwischenlager[schluessel] = fertig
return fertig