Darstellung: das Programm hatte ueberhaupt kein Theme

Sein Befund
  "und beim steuertool fehlt das theam in genze oder wird nicht durch die
   automatik gesetzt"
  "nur die einstellunden hilfe leiste die ist noch hell und der icon und
   titel leiste auch"

  Er hatte recht, und zwar wortwoertlich: dieses Programm lief im
  Tk-Standard, mit dreimal fest eingetragenem foreground="black". Es gab hier
  gar keine theme.py.

Gebaut
  theme.py aus dem Rechnungstool uebernommen und dabei von config.py geloest
  - eine Datei, die in BEIDEN Programmen liegt, darf nichts aus einem der
  beiden importieren. Beim ersten Uebernehmen flog sie genau daran beim
  Import auseinander.

  Die Einstellungen liegen hier in der Journal-Tabelle, nicht in einer
  config.json. _einstellungen() ist die Bruecke: etwas mit .get() und []=,
  wie hinweis.py und theme.py es erwarten.

  Menueleiste aus menueleiste.py statt tk.Menu - ein natives Menue laesst
  sich unter Windows nicht einfaerben, das war der letzte helle Rest.
  Treeview-Stil in theme.py ergaenzt (ttk zeichnet Listen nicht ueber den
  Grundstil), Reiter schmaler, damit alle zwoelf Monatsnamen lesbar bleiben.
  Die Summenleisten in gui_monat.py holen ihre Farben jetzt vom Fenster statt
  sie fest zu tragen - sie waren ein weisser Block unten im dunklen Bild.

  Logo und Fenstersymbol werden fuer die dunkle Darstellung aufgehellt.

⚠️ Altfehler behoben: die Meldung des Rechnungstools stand nie
  pruef_wache lief mal gruen und mal rot - je nachdem, wann er hinsah. Das
  war kein Wackler im Pruefstand, sondern sein Symptom: meldet das
  Rechnungstool eine neue Rechnung, laeuft sofort das Einlesen an, und dessen
  Ergebnis landete in derselben Zeile. Der Benutzer sah NIE, dass etwas
  hereingekommen war. melde() kennt jetzt einen Vorrang in Sekunden; die
  Meldung des Rechnungstools bekommt sechs. Dreimal hintereinander stabil
  gruen.

  Ein wackelnder Pruefstand ist keine Nebensache, die man wegdrueckt.

AUFGABE_STEUERSAETZE_DYNAMISCH.md
  Seine naechste Ansage notiert: die festen Saetze sollen raus, dynamisch per
  Tabelle wie im Rechnungstool. Mit dem, worauf dabei zu achten ist - ein
  Satz gilt ab einem DATUM, und die Saetze bereits erfasster Buchungen
  bleiben stehen (§ 147 AO).

Fassung 1.9.0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V4t48uxDok5rJXhC9bX1Ax
This commit is contained in:
TheMockTv 2026-09-07 00:34:38 +02:00
parent e4619259cb
commit 9d7aa1eec7
12 changed files with 1309 additions and 60 deletions

View file

@ -45,14 +45,51 @@ def pfad(name):
return ""
def symbol_setzen(fenster, melden=print):
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 = pfad("programm.ico")
p = (symbol_datei_dunkel() if dunkel else "") or pfad("programm.ico")
if not p:
melden("[symbol] assets/programm.ico nicht gefunden")
return False
@ -64,7 +101,45 @@ def symbol_setzen(fenster, melden=print):
return False
def hole(groesse=240, deckkraft=0.06, master=None, melden=print):
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
@ -77,7 +152,7 @@ def hole(groesse=240, deckkraft=0.06, master=None, melden=print):
fenster = master if master is not None else tk._default_root
if fenster is None:
return None
schluessel = (id(fenster), groesse, round(deckkraft, 3))
schluessel = (id(fenster), groesse, round(deckkraft, 3), bool(dunkel))
if schluessel in _lager:
return _lager[schluessel]
p = pfad("programm_logo.png")
@ -87,6 +162,8 @@ def hole(groesse=240, deckkraft=0.06, master=None, melden=print):
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
@ -96,11 +173,11 @@ def hole(groesse=240, deckkraft=0.06, master=None, melden=print):
return fertig
def kopf(hoehe=28, master=None, melden=print):
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)
return hole(hoehe, 1.0, master=master, melden=melden, dunkel=dunkel)