Seine Ansage "da gehen noch die popups auf dem video monitor auf" "einfach so programmieren das die popup auf dem fenster mittig vom main fenster auf gehen" Was falsch war An sechs Stellen stand `max(0, x)`. Das sollte verhindern, dass ein Dialog ausserhalb des Bildschirms aufgeht - klemmt aber jede NEGATIVE Koordinate auf null. Bei ihm steht der zweite Monitor links (x = -1920): jedes Popup sprang damit zurueck auf den Hauptbildschirm, obwohl das Programm nebenan lief. Dazu kamen feste Versaetze wie "+120+140", die mit der Fenstergroesse nichts zu tun hatten. theme.mittig(dialog, ueber) Eine Stelle fuer alle Dialoge: mittig ueber dem Elternfenster, etwas oberhalb der Mitte (ein Drittel statt der Haelfte), damit der Blick dort ist und die Mitte des Formulars frei bleibt. Das ist zugleich die Antwort auf die Monitorfrage: steht das Programm auf dem zweiten Bildschirm, geht der Dialog dort auf - ohne dass irgendwo ein Monitor gesucht werden muss. Die Sonderbehandlung im Testlauf konnte deshalb entfallen; geblieben ist dort nur der Verzicht auf grab_set. theme.auf_bildschirm(x, y, breite, hoehe) Begrenzt eine Position auf den GESAMTEN Desktop ueber alle Monitore (SM_*VIRTUALSCREEN) statt auf den Hauptmonitor. Das war die eigentliche Absicht hinter dem alten max(0, ...). Gemessen statt geglaubt: Dialogmitte und Fenstermitte liegen 8 Pixel auseinander (Fensterrahmen), der Dialog liegt innerhalb des Fensters. Steht als Pruefung in pruef_darstellung.py. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V4t48uxDok5rJXhC9bX1Ax
211 lines
8.8 KiB
Python
211 lines
8.8 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""Den gemeinsamen Ordner abfragen - in beiden Programmen gleich.
|
||
|
||
Rechnungstool und Steuerjournal fuehren ein gemeinsames Nummernbuch
|
||
(siehe gemeinsam.py). Damit sie sich finden, muss BEIDEN derselbe Ordner
|
||
genannt werden - deshalb fragt jedes Programm beim ersten Start danach und
|
||
sagt ausdruecklich dazu, dass es derselbe sein muss.
|
||
|
||
Der Weg ist bewusst umstaendlich: auswaehlen, dann OK, dann noch einmal
|
||
bestaetigen. Ein falscher Ordner faellt sonst erst auf, wenn eine
|
||
Rechnungsnummer zum zweiten Mal vergeben wurde.
|
||
|
||
Wer jetzt keinen Ordner hat, kommt mit "Spaeter einrichten" weiter - das
|
||
Programm laeuft dann ohne das gemeinsame Buch und sagt das in der Fussleiste.
|
||
|
||
⚠ Das Fenster bekommt seine Groesse ausdruecklich gesetzt. Ohne das war es zu
|
||
schmal, der Knopf zum Auswaehlen lag ausserhalb - und der Dialog war nicht zu
|
||
bedienen (gefunden am 03.09.2026, bevor es jemand benutzen musste).
|
||
|
||
Diese Datei liegt in BEIDEN Programmen gleich. Wer sie aendert, kopiert sie
|
||
hinueber.
|
||
"""
|
||
|
||
import os
|
||
import tkinter as tk
|
||
from tkinter import ttk, filedialog, messagebox
|
||
|
||
import theme
|
||
|
||
import gemeinsam
|
||
|
||
BREITE = 780
|
||
|
||
|
||
class OrdnerWahl(tk.Toplevel):
|
||
"""Modaler Dialog. Nach dem Schliessen steht das Ergebnis in `.ergebnis`
|
||
(gewaehlter Ordner) oder es ist None (spaeter/abgebrochen)."""
|
||
|
||
def __init__(self, master, programm: str, vorgabe: str = ""):
|
||
super().__init__(master)
|
||
self.ergebnis = None
|
||
self.title("Gemeinsamer Ordner für das Nummernbuch")
|
||
self.transient(master)
|
||
# Dieses Fenster stand als einziges noch in Weiss da: es kam nie durch
|
||
# _style_dialog. Farben holt es sich jetzt vom Hauptfenster, damit es
|
||
# nicht wieder auseinanderlaeuft, wenn dort etwas geaendert wird.
|
||
self._bg = getattr(master, "col_bg", "#0d0e10")
|
||
self._fg = getattr(master, "col_fg", "#e8e8ea")
|
||
self._warn = getattr(master, "col_gefahr", "#e85a5a")
|
||
self._gut = getattr(master, "col_accent", "#06C6A4")
|
||
self._muted = getattr(master, "col_muted", "#8a8e96")
|
||
if hasattr(master, "_style_dialog"):
|
||
master._style_dialog(self)
|
||
else:
|
||
self.configure(bg=self._bg)
|
||
self.pfad = tk.StringVar(value=vorgabe or "")
|
||
|
||
rahmen = ttk.Frame(self, padding=18)
|
||
rahmen.pack(fill="both", expand=True)
|
||
rahmen.columnconfigure(0, weight=1)
|
||
breite = BREITE - 60
|
||
|
||
ttk.Label(rahmen, text="Bitte den gemeinsamen Ordner auswählen",
|
||
font=(theme.SCHRIFT_NAME, 13, "bold")).grid(row=0, column=0, sticky="w")
|
||
ttk.Label(rahmen, wraplength=breite, justify="left", text=(
|
||
"Rechnungstool und Steuerjournal führen zusammen ein Nummernbuch. Darin steht "
|
||
"jede vergebene Rechnungsnummer – auch die aus der alten Excel-Tabelle, zu der "
|
||
"es gar keine PDF gibt.\n\n"
|
||
"Ohne dieses Buch kennt jedes Programm nur seine eigenen Nummern und vergibt "
|
||
"eine Nummer ein zweites Mal. Jede Nummer darf es aber nur einmal geben "
|
||
"(§ 14 Abs. 4 Nr. 4 UStG).")).grid(row=1, column=0, sticky="w", pady=(8, 12))
|
||
|
||
tk.Label(rahmen, wraplength=breite, justify="left", fg=self._warn, bg=self._bg,
|
||
font=(theme.SCHRIFT_NAME, 10, "bold"),
|
||
text="WICHTIG: In BEIDEN Programmen muss derselbe Ordner stehen – am besten "
|
||
"der Ordner, in dem die Programme liegen.").grid(
|
||
row=2, column=0, sticky="w", pady=(0, 14))
|
||
|
||
wahl = ttk.Frame(rahmen)
|
||
wahl.grid(row=3, column=0, sticky="ew")
|
||
wahl.columnconfigure(0, weight=1)
|
||
self.feld = ttk.Entry(wahl, textvariable=self.pfad, state="readonly")
|
||
self.feld.grid(row=0, column=0, sticky="ew")
|
||
ttk.Button(wahl, text="Install-Ordner auswählen…", command=self.waehlen,
|
||
width=26).grid(row=0, column=1, sticky="w", padx=(10, 0))
|
||
|
||
self.stand = tk.Label(rahmen, text="", wraplength=breite, justify="left",
|
||
bg=self._bg,
|
||
font=(theme.SCHRIFT_NAME, 9))
|
||
self.stand.grid(row=4, column=0, sticky="w", pady=(10, 0))
|
||
|
||
knoepfe = ttk.Frame(rahmen)
|
||
knoepfe.grid(row=5, column=0, sticky="ew", pady=(18, 0))
|
||
self.ok = ttk.Button(knoepfe, text="OK", command=self.uebernehmen, width=14)
|
||
self.ok.pack(side="right")
|
||
ttk.Button(knoepfe, text="Später einrichten", command=self.spaeter,
|
||
width=18).pack(side="left")
|
||
|
||
self._stand_zeigen()
|
||
self.protocol("WM_DELETE_WINDOW", self.spaeter)
|
||
self.bind("<Escape>", lambda _e: self.spaeter())
|
||
|
||
# Groesse ausdruecklich setzen: sonst schneidet Tk das Fenster an der
|
||
# zweiten Spalte ab und der Auswahl-Knopf ist nicht erreichbar.
|
||
self.update_idletasks()
|
||
hoehe = max(self.winfo_reqheight(), 300)
|
||
x = master.winfo_rootx() + max(0, (master.winfo_width() - BREITE) // 2)
|
||
y = master.winfo_rooty() + 90
|
||
self.geometry(f"{BREITE}x{hoehe}")
|
||
theme.mittig(self, master)
|
||
self.minsize(BREITE, hoehe)
|
||
self.grab_set()
|
||
self.lift()
|
||
self.focus_force()
|
||
|
||
# ------------------------------------------------------------------ Weg
|
||
def waehlen(self):
|
||
# Der Griff (grab_set) muss waehrend des Windows-Ordnerdialogs los sein:
|
||
# sonst kann der native Dialog hinter dem gesperrten Fenster landen und
|
||
# nimmt keine Eingabe an - man klickt und es passiert nichts.
|
||
try:
|
||
self.grab_release()
|
||
except tk.TclError:
|
||
pass
|
||
try:
|
||
ordner = filedialog.askdirectory(
|
||
title="Gemeinsamen Ordner wählen (in beiden Programmen derselbe!)",
|
||
initialdir=self.pfad.get() or os.path.expanduser("~"), parent=self)
|
||
finally:
|
||
try:
|
||
self.grab_set()
|
||
except tk.TclError:
|
||
pass
|
||
if ordner:
|
||
self.pfad.set(os.path.abspath(ordner))
|
||
self._stand_zeigen()
|
||
|
||
def _stand_zeigen(self):
|
||
"""Sagt, was in dem gewaehlten Ordner schon liegt.
|
||
|
||
Ein bereits vorhandenes Nummernbuch ist das beste Zeichen, dass es der
|
||
richtige Ordner ist - dann hat das andere Programm ihn schon.
|
||
"""
|
||
ordner = self.pfad.get().strip()
|
||
if not ordner:
|
||
self.stand.configure(text="Noch kein Ordner gewählt.", fg=self._warn)
|
||
self.ok.state(["disabled"])
|
||
return
|
||
taugt, grund = gemeinsam.ordner_taugt(ordner)
|
||
if not taugt:
|
||
self.stand.configure(text=grund, fg=self._warn)
|
||
self.ok.state(["disabled"])
|
||
return
|
||
self.ok.state(["!disabled"])
|
||
if os.path.exists(gemeinsam.pfad_zur_datei(ordner)):
|
||
try:
|
||
anzahl = len(gemeinsam.Nummernbuch(ordner).nummern())
|
||
except Exception: # noqa: BLE001
|
||
anzahl = "?"
|
||
self.stand.configure(
|
||
text=f"Hier liegt schon ein Nummernbuch mit {anzahl} Rechnungsnummern – "
|
||
"das ist der Ordner des anderen Programms. Genau richtig.",
|
||
fg=self._gut)
|
||
else:
|
||
self.stand.configure(
|
||
text="Hier ist noch kein Nummernbuch – es wird neu angelegt. Wähle im "
|
||
"anderen Programm denselben Ordner.", fg=self._muted)
|
||
|
||
def _melden(self, titel, text):
|
||
"""Meldung im Theme des Hauptfensters - sonst wie bisher."""
|
||
eltern = self.master
|
||
if hasattr(eltern, "melden_warnung"):
|
||
return eltern.melden_warnung(titel, text)
|
||
return messagebox.showwarning(titel, text, parent=self)
|
||
|
||
def _frage(self, titel, text):
|
||
eltern = self.master
|
||
if hasattr(eltern, "frage"):
|
||
return eltern.frage(titel, text)
|
||
return messagebox.askyesno(titel, text, parent=self)
|
||
|
||
|
||
def uebernehmen(self):
|
||
ordner = self.pfad.get().strip()
|
||
taugt, grund = gemeinsam.ordner_taugt(ordner)
|
||
if not taugt:
|
||
self._melden("Ordner geht nicht", grund or "Bitte einen Ordner wählen.")
|
||
return
|
||
if not self._frage(
|
||
"Sind Sie sicher?",
|
||
f"Gemeinsamer Ordner:\n\n{ordner}\n\n"
|
||
"Steht im anderen Programm derselbe Ordner? Nur dann kennen beide "
|
||
"dieselben Rechnungsnummern.\n\n"
|
||
"Ja = Ordner übernehmen\n"
|
||
"Nein = zurück zur Auswahl"):
|
||
# Zurueck ins Auswahlfeld - der Dialog bleibt stehen, nichts ist gesetzt.
|
||
self.feld.focus_set()
|
||
return
|
||
self.ergebnis = ordner
|
||
self.destroy()
|
||
|
||
def spaeter(self):
|
||
self.ergebnis = None
|
||
self.destroy()
|
||
|
||
|
||
def ordner_abfragen(fenster, programm, vorgabe=""):
|
||
"""Dialog zeigen und den gewaehlten Ordner zurueckgeben (oder None)."""
|
||
d = OrdnerWahl(fenster, programm, vorgabe)
|
||
fenster.wait_window(d)
|
||
return d.ergebnis
|