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
This commit is contained in:
TheMockTv 2026-09-05 19:53:42 +02:00
parent 5403e01dd5
commit c1e328c087
10 changed files with 561 additions and 17 deletions

82
app.py
View file

@ -39,6 +39,8 @@ from bestand import (hoechster_zaehler_im_ordner, naechste_freie_nummer,
vorhandene_rechnungen)
from einzelinstanz import (eigene_pid_merken, einzelinstanz_pruefen, fenster_nach_vorn,
laufende_pid_lesen)
import flaeche
import icons
import reiter
import theme
from theme import MeldungMixin, ThemeMixin
@ -102,9 +104,11 @@ class RechnungsApp(ThemeMixin, MeldungMixin, KorrekturMixin, EinstellungenMixin,
self._apply_theme() # Farben nach Windows hell/dunkel
self._build_menu()
self._build_kopf()
self._build_summen() # untere Leiste (Summen + Buttons) zuerst -> bleibt immer sichtbar
self._build_positionen() # Positionsliste fuellt den Rest und scrollt
self._build_flaeche() # Werkzeugleiste + verschiebbare Kaesten
self._build_kopf() # Rechnungsdaten + Kunde als Kaesten
self._build_positionen() # Leistungen als Kasten, fuellt den Rest
self.flaeche.anordnen()
self._neue_nummer_vorschlagen()
self.recalc()
@ -211,15 +215,62 @@ class RechnungsApp(ThemeMixin, MeldungMixin, KorrekturMixin, EinstellungenMixin,
pass
self._open_pop = None
# ------------------------------------------------------- Arbeitsflaeche
def _build_flaeche(self):
"""Die Kaesten und der Knopf, mit dem man sie verschieben darf.
Wie bei OPNsense: erst "Anordnen" druecken, dann schieben, dann
speichern. Ohne diesen Schritt sitzt alles fest - beim taeglichen
Arbeiten soll nichts verrutschen.
"""
leiste = ttk.Frame(self, padding=(16, 8, 16, 0))
leiste.pack(side="top", fill="x")
self.werkzeuge = ttk.Frame(leiste)
self.werkzeuge.pack(side="right")
self._ic_anordnen = icons.hole("dashboard_customize", 16, self.col_muted, master=self)
self._ic_speichern = icons.hole("check", 16, self.col_accent_hell, master=self)
self._ic_verwerfen = icons.hole("close", 16, self.col_muted, master=self)
self.knopf_anordnen = ttk.Button(self.werkzeuge, text=" Anordnen",
image=self._ic_anordnen, compound="left",
command=self._anordnen_umschalten)
self.knopf_anordnen.pack(side="left")
self.knopf_speichern = ttk.Button(self.werkzeuge, text=" Speichern",
image=self._ic_speichern, compound="left",
style="Haupt.TButton",
command=self._anordnen_speichern)
self.knopf_verwerfen = ttk.Button(self.werkzeuge, text=" Verwerfen",
image=self._ic_verwerfen, compound="left",
command=self._anordnen_verwerfen)
self.flaeche = flaeche.Arbeitsflaeche(self, farben=self)
self.flaeche.pack(side="top", fill="both", expand=True, padx=10, pady=(4, 0))
def _anordnen_umschalten(self):
self.flaeche.anordnen_starten()
self.knopf_anordnen.pack_forget()
self.knopf_verwerfen.pack(side="left", padx=(0, 6))
self.knopf_speichern.pack(side="left")
def _anordnen_fertig(self):
self.knopf_speichern.pack_forget()
self.knopf_verwerfen.pack_forget()
self.knopf_anordnen.pack(side="left")
def _anordnen_speichern(self):
self.flaeche.anordnen_speichern()
self._anordnen_fertig()
def _anordnen_verwerfen(self):
self.flaeche.anordnen_verwerfen()
self._anordnen_fertig()
# ---------------------------------------------------------------- Kopf
def _build_kopf(self):
top = ttk.Frame(self, padding=(16, 14, 16, 6))
top.pack(fill="x")
# Kunde (links)
kunde = ttk.LabelFrame(top, text="RECHNUNG AN (KUNDE)", padding=14,
style="Karte.TLabelframe")
kunde.grid(row=0, column=0, sticky="nsew", padx=(0, 14))
# Beide Bloecke sind Kaesten der Arbeitsflaeche - sie lassen sich
# verschieben, deshalb platziert sie niemand mehr von Hand.
kunde = self.flaeche.kasten("kunde", "Rechnung an (Kunde)")
self.k_anrede = tk.StringVar()
self.k_vorname = tk.StringVar()
self.k_name = tk.StringVar() # Nachname oder Firma – die Zuordnung im Steuerjournal
@ -249,10 +300,7 @@ class RechnungsApp(ThemeMixin, MeldungMixin, KorrekturMixin, EinstellungenMixin,
row=len(felder), column=0, columnspan=2, sticky="w", pady=(6, 0))
kunde.columnconfigure(1, weight=1)
# Rechnungsinfos (rechts)
info = ttk.LabelFrame(top, text="RECHNUNGSDATEN", padding=14,
style="Karte.TLabelframe")
info.grid(row=0, column=1, sticky="nsew")
info = self.flaeche.kasten("daten", "Rechnungsdaten")
self.r_nummer = tk.StringVar()
self.r_datum = tk.StringVar(value=datetime.date.today().strftime("%d.%m.%Y"))
self.r_anreise = tk.StringVar()
@ -296,8 +344,7 @@ class RechnungsApp(ThemeMixin, MeldungMixin, KorrekturMixin, EinstellungenMixin,
self.r_anreise.trace_add("write", lambda *a: self._update_naechte())
self.r_abreise.trace_add("write", lambda *a: self._update_naechte())
top.columnconfigure(0, weight=1)
top.columnconfigure(1, weight=0)
def _auto_datum_evt(self, event):
"""Punkte automatisch setzen, Cursor ans Ende. Nach Tag+Monat das aktuelle
@ -378,8 +425,9 @@ class RechnungsApp(ThemeMixin, MeldungMixin, KorrekturMixin, EinstellungenMixin,
# Eigene Reiterleiste statt ttk.Notebook: unter clam liess sich der
# ruhende Reiter nicht dunkel bekommen, er sah aus wie der offene.
# Nach aussen verhaelt sie sich wie ein Notebook (siehe reiter.py).
self.blaetter = reiter.Reiterleiste(self, farben=self)
self.blaetter.pack(side="top", fill="both", expand=True, padx=16, pady=(4, 6))
leistungen = self.flaeche.kasten("leistungen", "Leistungen")
self.blaetter = reiter.Reiterleiste(leistungen, farben=self)
self.blaetter.pack(side="top", fill="both", expand=True)
self.blaetter.bind("<<NotebookTabChanged>>", lambda _e: self._blatt_gewechselt())
camping = ttk.Frame(self.blaetter.bereich, padding=(2, 6))