Fixes aus den Audits (Fable/Codex): Doppelte, Staffel, Export-Sperre, Netz-DB, atomare Einstellungen, Formeln, Wiederherstellung, Importgrenzen, schnellere Suche; toter Code entfernt; Version 1.8.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
72677ebed7
commit
968c68ac1a
12 files changed
+536
-178
No files matched your search
+130
-56
@@ -61,11 +61,14 @@ class App:
|
||||
def __init__(self, root: tk.Tk):
|
||||
self.root = root
|
||||
self.settings = config.load()
|
||||
self.db = Datenbank(config.db_path(self.settings))
|
||||
self.db: Datenbank | None = None # wird nach dem Aufbau des Fensters geöffnet (_db_oeffnen)
|
||||
self.aid: int | None = None
|
||||
self.positionen: list[dict] = []
|
||||
self.haendler_namen: dict[int, str] = {}
|
||||
self.ansicht: int = ALLE
|
||||
self._alle_gruppen: dict[str, tuple[str, list[dict]]] = {}
|
||||
self._gruppen: dict[str, tuple[str, list[dict]]] = {}
|
||||
self._zeilen_tags: dict[str, tuple[str, ...]] = {}
|
||||
self._edit: tk.Entry | None = None
|
||||
self._schliesst = False # Beenden angefordert (offene Dialoge werden gerade geschlossen)
|
||||
self._beendet = False # Datenbank und Fenster sind zu
|
||||
@@ -101,16 +104,51 @@ class App:
|
||||
if self.settings.get("trenner"):
|
||||
root.after(200, lambda: self.pw.sash_place(0, int(self.settings["trenner"]), 0))
|
||||
|
||||
self.db = self._db_oeffnen()
|
||||
abr = self.db.abrechnungen()
|
||||
if abr:
|
||||
self.oeffnen(abr[0]["id"])
|
||||
else:
|
||||
self._aktualisieren()
|
||||
if not self.settings.get("assistent_erledigt") and not self.db.haendler_liste():
|
||||
if self.settings.pop("defekt", None):
|
||||
root.after(300, lambda: w.warnung(
|
||||
root, "Einstellungen",
|
||||
"Die Einstellungsdatei war unlesbar und wurde durch Standardwerte ersetzt (Kopie: "
|
||||
f"{config.app_dir() / 'einstellungen.defekt.json'}).\n\nBitte unter Datei > Einstellungen "
|
||||
"prüfen, ob der Speicherort der Datenbank noch stimmt."))
|
||||
elif not self.settings.get("assistent_erledigt") and not self.db.haendler_liste():
|
||||
root.after(400, self.assistent)
|
||||
elif self.settings.get("tipps_beim_start", True):
|
||||
root.after(500, self.tipp_des_tages)
|
||||
|
||||
def _db_oeffnen(self) -> Datenbank:
|
||||
"""Datenbank laut Einstellungen öffnen. Ist ein eigener Speicherort eingestellt (Netzlaufwerk) und
|
||||
nicht erreichbar, wird gefragt – statt abzustürzen oder dort still eine leere Datenbank anzulegen."""
|
||||
while True:
|
||||
pfad = config.db_path(self.settings)
|
||||
try:
|
||||
if self.settings.get("db_pfad") and not pfad.exists():
|
||||
raise FileNotFoundError("Die Datei gibt es nicht (Netzlaufwerk nicht verbunden?)")
|
||||
return Datenbank(pfad)
|
||||
except Exception as exc:
|
||||
wahl = w._meldung(self.root, "Datenbank nicht erreichbar",
|
||||
f"Die Datenbank konnte nicht geöffnet werden:\n{pfad}\n\n{exc}\n\n"
|
||||
"Standard: die lokale Datenbank dieses PCs verwenden.",
|
||||
"fehler", [("Standard", "standard"), ("Andere wählen...", "waehlen"),
|
||||
("Beenden", "ende")], default=0)
|
||||
if wahl == "standard":
|
||||
self.settings["db_pfad"] = None
|
||||
config.save(self.settings)
|
||||
elif wahl == "waehlen":
|
||||
p = filedialog.askopenfilename(parent=self.root, title="Datenbank öffnen",
|
||||
filetypes=[("Datenbank", "*.db"), ("Alle Dateien", "*.*")])
|
||||
if p:
|
||||
self.settings["db_pfad"] = p
|
||||
config.save(self.settings)
|
||||
else:
|
||||
self.root.destroy()
|
||||
raise SystemExit(1) from exc
|
||||
|
||||
# ================================================================= Aufbau
|
||||
def _kopf(self):
|
||||
"""Kopfzeile wie in den Webtools: Logo, Menüs, rechts Suche (modern auch Hell/Dunkel)."""
|
||||
@@ -378,7 +416,7 @@ class App:
|
||||
self.filter = key
|
||||
for k, pill in self.pills.items():
|
||||
pill.setzen(k == key)
|
||||
self._tabelle()
|
||||
self._filtern()
|
||||
|
||||
def _tasten(self):
|
||||
r = self.root
|
||||
@@ -405,9 +443,10 @@ class App:
|
||||
|
||||
def _aktualisieren(self, behalte_auswahl: bool = True):
|
||||
self.haendler_namen = {h.id: h.name for h in self.db.haendler_liste()}
|
||||
if self.aid and self.db.abrechnung(self.aid):
|
||||
a = self.db.abrechnung(self.aid) if self.aid else None
|
||||
if a:
|
||||
self.positionen = self.db.positionen(self.aid)
|
||||
titel = self.db.abrechnung(self.aid)["titel"] + (" – erledigt" if self.db.erledigt(self.aid) else "")
|
||||
titel = a["titel"] + (" – erledigt" if a["erledigt"] else "")
|
||||
self.rahmen.set_titel(f"{w.APP_NAME} - [{titel}]")
|
||||
self.status.set("info", f" Abrechnung: {titel}")
|
||||
else:
|
||||
@@ -416,20 +455,19 @@ class App:
|
||||
self.rahmen.set_titel(w.APP_NAME)
|
||||
self.status.set("info", " Keine Abrechnung geöffnet – Datei > Neue Abrechnung oder Import")
|
||||
self._abrechnungsliste()
|
||||
self._seitenkopf()
|
||||
self._seitenkopf(a)
|
||||
self._haendlerliste(behalte_auswahl)
|
||||
self._tabelle()
|
||||
|
||||
def _seitenkopf(self):
|
||||
"""Titel, Status und Knöpfe über den Kennzahlen."""
|
||||
a = self.db.abrechnung(self.aid) if self.aid else None
|
||||
def _seitenkopf(self, a):
|
||||
"""Titel, Status und Knöpfe über den Kennzahlen (a: Zeile der Abrechnung oder None)."""
|
||||
if not a:
|
||||
self.l_status.configure(text="Keine Abrechnung geöffnet".upper() if w.MODERN else "Keine Abrechnung")
|
||||
self.l_titel.configure(text="Willkommen")
|
||||
self.l_info.configure(text="Links „+ Neu“ anlegen oder gleich Dateien importieren.")
|
||||
self.b_erledigt.configure(text="Als erledigt markieren", state="disabled")
|
||||
return
|
||||
erledigt = self.db.erledigt(self.aid)
|
||||
erledigt = a["erledigt"]
|
||||
dateien = self.db.dateien(self.aid)
|
||||
status = "erledigt" if erledigt else "offen"
|
||||
self.l_status.configure(text=f"Abrechnung · {status}".upper() if w.MODERN else f"Abrechnung ({status})")
|
||||
@@ -463,7 +501,7 @@ class App:
|
||||
|
||||
def _suche_ausfuehren(self):
|
||||
self._suche_geplant = None
|
||||
self._tabelle()
|
||||
self._filtern()
|
||||
|
||||
def _gefiltert(self) -> list[dict]:
|
||||
if self.ansicht == ALLE:
|
||||
@@ -521,6 +559,8 @@ class App:
|
||||
return f"{ANBIETER.get(p['anbieter'], p['anbieter'])} · {p['provisionsart'] or '(ohne Provisionsart)'}"
|
||||
|
||||
def _tabelle(self):
|
||||
"""Baut die Tabelle für die gewählte Ansicht komplett neu auf (nach Datenänderungen).
|
||||
Suche und Filter blenden danach nur noch Zeilen aus und ein (_filtern) – kein Neuaufbau."""
|
||||
if self._edit:
|
||||
self._edit.destroy()
|
||||
self._edit = None
|
||||
@@ -528,49 +568,80 @@ class App:
|
||||
offen = {iid for iid in t.get_children() if t.item(iid, "open")}
|
||||
war_leer = not t.get_children()
|
||||
t.delete(*t.get_children())
|
||||
such = self.v_suche.get().strip().lower()
|
||||
pos = self._gefiltert()
|
||||
if such:
|
||||
pos = [p for p in pos if such in " ".join(str(p.get(k) or "") for k in (
|
||||
"rufnummer", "vorname", "name", "tarif", "provisionsart", "kennung", "zeitraum", "aktionscode",
|
||||
"imei")).lower()]
|
||||
pruef = {"unzugeordnet": lambda p: p["haendler_id"] == UNZUGEORDNET, "manuell": lambda p: p["manuell"],
|
||||
"doppelt": lambda p: p["doppelt"], "abgerechnet": lambda p: p["abgerechnet"]}.get(self.filter)
|
||||
if pruef:
|
||||
pos = [p for p in pos if pruef(p)]
|
||||
gruppen: dict[str, list[dict]] = defaultdict(list)
|
||||
for p in exp.sortiert(pos):
|
||||
for p in exp.sortiert(self._gefiltert()):
|
||||
gruppen[self._gruppe_von(p)].append(p)
|
||||
self._gruppen = {}
|
||||
self._alle_gruppen = {} # giid -> (Name, alle Positionen der Gruppe)
|
||||
self._zeilen_tags = {}
|
||||
for gi, (gname, plist) in enumerate(sorted(gruppen.items())):
|
||||
giid = f"g{gi}"
|
||||
self._gruppen[giid] = (gname, plist)
|
||||
so = sum(p["betrag_orig"] for p in plist)
|
||||
sa = sum(p["betrag"] for p in plist)
|
||||
proz = {p["prozent"] for p in plist}
|
||||
if all(_ist_staffel(p) for p in plist):
|
||||
pz = "Staffel"
|
||||
else:
|
||||
pz = fmt_percent(next(iter(proz))) if len(proz) == 1 and None not in proz else ("gemischt" if so else "")
|
||||
t.insert("", "end", iid=giid, text=f"{gname} ({len(plist)})", open=(war_leer or giid in offen or bool(such)),
|
||||
values=("", "", "", "", "", fmt(so), pz, fmt(sa)), tags=("gruppe",))
|
||||
for i, p in enumerate(plist):
|
||||
tags = ["zebra"] if i % 2 else []
|
||||
self._alle_gruppen[giid] = (gname, plist)
|
||||
t.insert("", "end", iid=giid, text="", open=(war_leer or giid in offen), tags=("gruppe",))
|
||||
for p in plist:
|
||||
tags: tuple[str, ...] = ()
|
||||
if p["abgerechnet"] or p["doppelt"]:
|
||||
tags.append("abgerechnet")
|
||||
tags = ("abgerechnet",)
|
||||
elif p["manuell"]:
|
||||
tags.append("manuell")
|
||||
tags = ("manuell",)
|
||||
elif p["betrag"] != p["betrag_orig"]:
|
||||
tags.append("abw")
|
||||
tags = ("abw",)
|
||||
self._zeilen_tags[f"p{p['id']}"] = tags
|
||||
kunde = " ".join(x for x in (p["vorname"], p["name"]) if x) or "(ohne Name)"
|
||||
t.insert(giid, "end", iid=f"p{p['id']}", text=kunde,
|
||||
values=(_datum_de(p["datum"]), p["rufnummer"], p["tarif"], _zeitraum_kurz(p["zeitraum"]),
|
||||
p["kennung"] + (" (fest)" if p["haendler_fest"] is not None else ""),
|
||||
fmt(p["betrag_orig"]),
|
||||
"abgerechnet" if p["abgerechnet"] or p["doppelt"] else
|
||||
"abgerechnet" if p["abgerechnet"] else "doppelt" if p["doppelt"] else
|
||||
"manuell" if p["manuell"] else ("Staffel" if _ist_staffel(p) else fmt_percent(p["prozent"])),
|
||||
fmt(p["betrag"])),
|
||||
tags=tuple(tags))
|
||||
tags=tags)
|
||||
self._filtern()
|
||||
|
||||
def _filtern(self):
|
||||
"""Suche und Filter anwenden: nicht passende Zeilen werden nur ausgehängt (detach), passende wieder
|
||||
eingehängt – viel schneller als Löschen und Neuanlegen. Gruppenzeilen und Kennzahlen folgen."""
|
||||
t = self.ptree
|
||||
such = self.v_suche.get().strip().lower()
|
||||
pruef = {"unzugeordnet": lambda p: p["haendler_id"] == UNZUGEORDNET, "manuell": lambda p: p["manuell"],
|
||||
"doppelt": lambda p: p["doppelt"], "abgerechnet": lambda p: p["abgerechnet"]}.get(self.filter)
|
||||
|
||||
def passt(p: dict) -> bool:
|
||||
if pruef and not pruef(p):
|
||||
return False
|
||||
return not such or such in " ".join(str(p.get(k) or "") for k in (
|
||||
"rufnummer", "vorname", "name", "tarif", "provisionsart", "kennung", "zeitraum", "aktionscode",
|
||||
"imei")).lower()
|
||||
|
||||
self._gruppen = {} # giid -> (Name, sichtbare Positionen) – für Auswahl über die Gruppenzeile
|
||||
pos: list[dict] = []
|
||||
sichtbar_gi = 0
|
||||
for giid, (gname, plist) in self._alle_gruppen.items():
|
||||
treffer = []
|
||||
for p in plist:
|
||||
iid = f"p{p['id']}"
|
||||
if passt(p):
|
||||
t.move(iid, giid, len(treffer))
|
||||
t.item(iid, tags=self._zeilen_tags[iid] + (("zebra",) if len(treffer) % 2 else ()))
|
||||
treffer.append(p)
|
||||
else:
|
||||
t.detach(iid)
|
||||
if not treffer:
|
||||
t.detach(giid)
|
||||
continue
|
||||
t.move(giid, "", sichtbar_gi)
|
||||
sichtbar_gi += 1
|
||||
self._gruppen[giid] = (gname, treffer)
|
||||
pos += treffer
|
||||
so = sum(p["betrag_orig"] for p in treffer)
|
||||
sa = sum(p["betrag"] for p in treffer)
|
||||
proz = {p["prozent"] for p in treffer}
|
||||
if all(_ist_staffel(p) for p in treffer):
|
||||
pz = "Staffel"
|
||||
else:
|
||||
pz = fmt_percent(next(iter(proz))) if len(proz) == 1 and None not in proz else ("gemischt" if so else "")
|
||||
t.item(giid, text=f"{gname} ({len(treffer)})", values=("", "", "", "", "", fmt(so), pz, fmt(sa)))
|
||||
if such:
|
||||
t.item(giid, open=True)
|
||||
name = {ALLE: "Alle Positionen", UNZUGEORDNET: "Nicht zugeordnet",
|
||||
EIGEN: "Eigene Kennungen (ausgeblendet, werden nicht exportiert)"}.get(
|
||||
self.ansicht, self.haendler_namen.get(self.ansicht, ""))
|
||||
@@ -616,13 +687,6 @@ class App:
|
||||
return list(dict.fromkeys(pids))
|
||||
|
||||
# ================================================================= Aktionen
|
||||
def _brauche_abrechnung(self, frage_neu=True) -> bool:
|
||||
if self.aid:
|
||||
return True
|
||||
if frage_neu:
|
||||
return self.neu()
|
||||
return False
|
||||
|
||||
def _vorschlag_titel(self) -> str:
|
||||
heute = date.today()
|
||||
m = heute.month - 1 or 12
|
||||
@@ -680,6 +744,7 @@ class App:
|
||||
if self._gesperrt():
|
||||
return
|
||||
bericht = []
|
||||
importiert: list[tuple[int, int, list[str]]] = [] # (Zeile im Bericht, datei_id, Hinweise)
|
||||
self.root.configure(cursor="watch")
|
||||
self.root.update()
|
||||
try:
|
||||
@@ -701,15 +766,20 @@ class App:
|
||||
bericht.append((name, erg.format, 0, 0, "übersprungen (bereits importiert)"))
|
||||
continue
|
||||
n = self.db.importieren(self.aid, erg)
|
||||
did = self.db.dateien(self.aid)[-1]["id"]
|
||||
doppelt = sum(1 for p in self.db.positionen(self.aid) if p["datei_id"] == did and p["doppelt"])
|
||||
hinweise = list(erg.warnungen) + ([f"{doppelt} Pos. bereits abgerechnet (werden nicht erneut "
|
||||
"exportiert)"] if doppelt else [])
|
||||
bericht.append((name, erg.format, n, sum(p.betrag_cent for p in erg.positionen),
|
||||
"; ".join(hinweise) or "OK"))
|
||||
importiert.append((len(bericht), self.db.dateien(self.aid)[-1]["id"], list(erg.warnungen)))
|
||||
bericht.append((name, erg.format, n, sum(p.betrag_cent for p in erg.positionen), ""))
|
||||
finally:
|
||||
self.root.configure(cursor="")
|
||||
self._aktualisieren()
|
||||
# Doppelte (schon abgerechnet oder schon offen vorhanden) je Datei zählen – einmal für alle Dateien
|
||||
doppelt: dict[int, int] = defaultdict(int)
|
||||
for p in self.positionen:
|
||||
if p["doppelt"]:
|
||||
doppelt[p["datei_id"]] += 1
|
||||
for zeile, did, hinweise in importiert:
|
||||
if doppelt.get(did):
|
||||
hinweise.append(f"{doppelt[did]} Pos. doppelt bzw. bereits abgerechnet (werden nicht exportiert)")
|
||||
bericht[zeile] = bericht[zeile][:4] + ("; ".join(hinweise) or "OK",)
|
||||
unbekannt: dict[tuple[str, str], list[int]] = defaultdict(lambda: [0, 0])
|
||||
for p in self.positionen:
|
||||
if p["haendler_id"] == UNZUGEORDNET:
|
||||
@@ -1071,6 +1141,8 @@ class App:
|
||||
w.info(self.root, "Export", "Diese Abrechnung enthält noch keine Positionen.\n"
|
||||
"Bitte zuerst Dateien importieren.")
|
||||
return
|
||||
if self._gesperrt(): # erledigt: nichts mehr exportieren (und damit abrechnen)
|
||||
return
|
||||
# nur offene Positionen echter Händler (nicht: nicht zugeordnet, eigene, schon abgerechnet)
|
||||
offen = [p for p in self.positionen if p["haendler_id"] > 0 and not p["abgerechnet"] and not p["doppelt"]]
|
||||
stat: dict[int, list[int]] = defaultdict(lambda: [0, 0])
|
||||
@@ -1095,12 +1167,14 @@ class App:
|
||||
config.save(self.settings)
|
||||
erzeugt, exportiert = [], []
|
||||
zeit = res["zeitraum"]
|
||||
vergeben: set[str] = set() # Dateinamen dieses Exports – gleich lautende Händler überschreiben sich nicht
|
||||
try:
|
||||
for hid in res["ids"]:
|
||||
pos = [p for p in offen if p["haendler_id"] == hid and (zeit is None or zeit.enthaelt(p))]
|
||||
erzeugt += exp.exportiere_haendler(res["ordner"], self.haendler_namen[hid], pos, res["titel"],
|
||||
pro_anbieter=res["pro_anbieter"], kopfzeile=res["kopfzeile"],
|
||||
summe=res["summe"], null_weglassen=res["null_weglassen"])
|
||||
summe=res["summe"], null_weglassen=res["null_weglassen"],
|
||||
vergeben=vergeben)
|
||||
exportiert += pos
|
||||
except PermissionError as exc:
|
||||
w.fehler(self.root, "Export", f"Die Datei konnte nicht geschrieben werden:\n{exc.filename}\n\n"
|
||||
@@ -1346,13 +1420,13 @@ class App:
|
||||
try:
|
||||
self.settings["trenner"] = self.pw.sash_coord(0)[0]
|
||||
self.settings["fenster"] = self.rahmen.normale_geometrie()
|
||||
self.settings["maximiert"] = self.rahmen.maximiert
|
||||
config.save(self.settings)
|
||||
except (tk.TclError, OSError):
|
||||
pass
|
||||
finally:
|
||||
try:
|
||||
self.db.close()
|
||||
if self.db is not None:
|
||||
self.db.close()
|
||||
finally:
|
||||
try:
|
||||
self.root.destroy()
|
||||
|
||||
+11
-2
@@ -14,7 +14,6 @@ from provtool.money import fmt, fmt_percent, parse_cent, parse_percent
|
||||
|
||||
# Auswahl im Händler-Dialog: Schlüssel -> Anzeige
|
||||
KENNUNG_ARTEN = {"freenet": "freenet (Unterhändler-Nr.)", "vodafone": "Vodafone (VO-Nr.)"}
|
||||
CAPS_TEXT = "Kunden-Nachname komplett in GROSSBUCHSTABEN"
|
||||
CAPS_HAKEN = "Vodafone-Kunden mit Nachnamen in GROSSBUCHSTABEN gehören zu diesem Händler"
|
||||
|
||||
|
||||
@@ -295,6 +294,12 @@ class HaendlerDialog(w.Dialog):
|
||||
return
|
||||
if self.v_kwert.get().strip():
|
||||
self._kennung_dazu()
|
||||
if self.aktuell and self.aktuell.aktiv and not self.v_aktiv.get() and not w.frage(
|
||||
self, "Händler deaktivieren",
|
||||
f"„{name}“ inaktiv setzen?\n\nSeine noch offenen Positionen gelten dann als „nicht zugeordnet“ "
|
||||
"und zeigen wieder die volle Provision (100 %); exportiert werden sie nicht. Bereits "
|
||||
"abgerechnete Positionen bleiben, wie sie sind.", ja="Inaktiv setzen", default_ja=False):
|
||||
return
|
||||
h = Haendler(self.aktuell.id if self.aktuell else 0, name, self.v_ap.get().strip(), self.v_mail.get().strip(),
|
||||
self.t_notiz.get("1.0", "end").strip(), proz, self.v_aktiv.get(), air,
|
||||
self._eigene_staffel if self.v_eigen_staffel.get() else None)
|
||||
@@ -628,7 +633,11 @@ class AbrechnungenDialog(w.Dialog):
|
||||
titel = self.db.abrechnung(aid)["titel"]
|
||||
if w.frage(self, "Löschen", f"Abrechnung „{titel}“ mit allen Positionen und Änderungen endgültig löschen?",
|
||||
default_ja=False):
|
||||
self.db.abrechnung_loeschen(aid)
|
||||
try:
|
||||
self.db.abrechnung_loeschen(aid)
|
||||
except AbrechnungErledigt as exc:
|
||||
w.info(self, "Löschen", str(exc))
|
||||
return
|
||||
self._laden()
|
||||
self.result = ("geloescht", aid)
|
||||
|
||||
|
||||
@@ -191,11 +191,6 @@ def ist_bedienbar(fenster: tk.Misc) -> bool:
|
||||
return bool(user32().IsWindowEnabled(h)) if h else True
|
||||
|
||||
|
||||
def letztes_popup(fenster: tk.Misc) -> int | None:
|
||||
h = hwnd(fenster)
|
||||
return user32().GetLastActivePopup(h) if h else None
|
||||
|
||||
|
||||
class Modal:
|
||||
"""Macht `fenster` modal zu `besitzer` (Windows-Art). beenden() muss VOR dem Zerstören des
|
||||
Fensters laufen: erst Besitzer wieder freigeben, dann schließen – sonst aktiviert Windows
|
||||
|
||||
+1
-58
@@ -593,34 +593,6 @@ class Sunken(tk.Frame):
|
||||
super().__init__(master, **kw)
|
||||
|
||||
|
||||
class Fenster(tk.Frame):
|
||||
"""Ein „Fenster im Fenster“ mit blauer Titelleiste – reine Optik, gliedert die Oberfläche."""
|
||||
|
||||
def __init__(self, master, titel: str, icon: tk.PhotoImage | None = None, **kw):
|
||||
if MODERN: # schlichte Überschrift statt blauer Titelleiste
|
||||
super().__init__(master, bd=0, bg=FACE, **kw)
|
||||
self.titel_var = tk.StringVar(value=titel)
|
||||
self.titelleiste = tk.Label(self, textvariable=self.titel_var, bg=FACE, fg=DARK, font=FONT_BOLD,
|
||||
anchor="w")
|
||||
self.titelleiste.pack(fill="x", pady=(0, 4))
|
||||
self.inhalt = tk.Frame(self, bg=FACE)
|
||||
self.inhalt.pack(fill="both", expand=True)
|
||||
return
|
||||
super().__init__(master, bd=2, relief="raised", bg=FACE, **kw)
|
||||
self.titelleiste = tk.Frame(self, bg=NAVY, height=18)
|
||||
self.titelleiste.pack(fill="x", padx=1, pady=1)
|
||||
if icon is not None:
|
||||
tk.Label(self.titelleiste, image=icon, bg=NAVY).pack(side="left", padx=(2, 0))
|
||||
self.titel_var = tk.StringVar(value=titel)
|
||||
tk.Label(self.titelleiste, textvariable=self.titel_var, bg=NAVY, fg=WHITE, font=FONT_BOLD,
|
||||
anchor="w").pack(side="left", fill="x", padx=3)
|
||||
self.inhalt = tk.Frame(self, bg=FACE)
|
||||
self.inhalt.pack(fill="both", expand=True, padx=2, pady=2)
|
||||
|
||||
def set_titel(self, t: str):
|
||||
self.titel_var.set(t)
|
||||
|
||||
|
||||
class Statusleiste(tk.Frame):
|
||||
def __init__(self, master, felder: list[tuple[str, int]]):
|
||||
super().__init__(master, bg=FACE)
|
||||
@@ -671,7 +643,7 @@ class Karte(tk.Frame):
|
||||
|
||||
|
||||
def titelleiste(master, text: str = "") -> tuple[tk.Frame, tk.Label]:
|
||||
"""Win95: blaue Titelleiste oben in einer Karte (wie bei Fenster); rechts passen kleine Knöpfe hinein."""
|
||||
"""Win95: blaue Titelleiste oben in einer Karte („Fenster im Fenster“); rechts passen kleine Knöpfe hinein."""
|
||||
f = tk.Frame(master, bg=NAVY)
|
||||
f.pack(fill="x", padx=1, pady=1)
|
||||
lbl = tk.Label(f, text=text, bg=NAVY, fg=WHITE, font=FONT_BOLD, anchor="w", padx=3)
|
||||
@@ -768,35 +740,6 @@ def logo(master, groesse: int = 34) -> tk.Canvas:
|
||||
return c
|
||||
|
||||
|
||||
class ToolbarButton(tk.Label):
|
||||
"""Flacher Werkzeugleisten-Knopf (Office-95-Stil): hebt sich beim Überfahren, senkt sich beim Klick."""
|
||||
|
||||
def __init__(self, master, text: str, icon: tk.PhotoImage, command, tooltip: str = ""):
|
||||
super().__init__(master, text=text, image=icon, compound="top", bd=1, relief="flat", bg=FACE,
|
||||
padx=6, pady=2, font=FONT)
|
||||
self.command = command
|
||||
if MODERN: # Hervorhebung durch Farbe statt 3D-Kante
|
||||
self.configure(bd=0, padx=8, pady=4)
|
||||
self.bind("<Enter>", lambda e: self.configure(bg=LIGHT))
|
||||
self.bind("<Leave>", lambda e: self.configure(bg=FACE))
|
||||
self.bind("<ButtonPress-1>", lambda e: self.configure(bg=SEL_BG))
|
||||
self.bind("<ButtonRelease-1>", self._release)
|
||||
if tooltip:
|
||||
Tooltip(self, tooltip)
|
||||
return
|
||||
self.bind("<Enter>", lambda e: self.configure(relief="raised"))
|
||||
self.bind("<Leave>", lambda e: self.configure(relief="flat"))
|
||||
self.bind("<ButtonPress-1>", lambda e: self.configure(relief="sunken"))
|
||||
self.bind("<ButtonRelease-1>", self._release)
|
||||
if tooltip:
|
||||
Tooltip(self, tooltip)
|
||||
|
||||
def _release(self, e):
|
||||
self.configure(bg=LIGHT) if MODERN else self.configure(relief="raised")
|
||||
if 0 <= e.x < self.winfo_width() and 0 <= e.y < self.winfo_height() and self.command:
|
||||
self.command()
|
||||
|
||||
|
||||
class Tooltip:
|
||||
def __init__(self, widget, text: str):
|
||||
self.widget, self.text, self.tip, self._after = widget, text, None, None
|
||||
|
||||
Reference in new issue
Block a user