update
This commit is contained in:
577
AzA march 2026/aza_launcher.py
Normal file
577
AzA march 2026/aza_launcher.py
Normal file
@@ -0,0 +1,577 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
AZA Desktop – Launcher / Startseite.
|
||||
Premium-Medizinprodukt-Design mit 6 Modulkacheln, KI-Kapazitätsanzeige
|
||||
und verstecktem Admin-Zugang (Doppelklick auf Logo).
|
||||
"""
|
||||
|
||||
import os
|
||||
import tkinter as tk
|
||||
|
||||
from aza_config import (
|
||||
LAUNCHER_MODULES,
|
||||
LAUNCHER_MODULE_LABELS,
|
||||
)
|
||||
from aza_persistence import (
|
||||
load_launcher_prefs,
|
||||
save_launcher_prefs,
|
||||
get_remaining_tokens,
|
||||
get_capacity_fraction,
|
||||
estimated_reports_remaining,
|
||||
is_capacity_low,
|
||||
)
|
||||
from aza_ui_helpers import save_toplevel_geometry, load_toplevel_geometry
|
||||
from aza_style import (
|
||||
BG, CARD_BG, CARD_HOVER_BG, CARD_BORDER, CARD_HOVER_BORDER,
|
||||
ACCENT, TEXT, SUBTLE,
|
||||
CAPACITY_BLUE, TURQUOISE, WARNING_AMBER, DANGER,
|
||||
FONT_FAMILY, SPACING,
|
||||
format_number_de,
|
||||
)
|
||||
|
||||
|
||||
_MODULE_DESCRIPTIONS = {
|
||||
"ki": "Medizinische Fragen stellen,\nBefunde besprechen, Zweitmeinung einholen",
|
||||
"kg": "Diktat aufnehmen, transkribieren\nund Krankengeschichte erstellen",
|
||||
"notizen": "Sprachaufnahmen und Notizen\nfür den Praxisalltag",
|
||||
"translator": "Medizinische Fachtexte übersetzen\nund Begriffe nachschlagen",
|
||||
"medwork_chat": "Kollegialer Austausch mit\nÄrzten und Fachpersonal",
|
||||
"praxis_chat": "Nachrichten und Aufgaben\nim eigenen Praxisteam",
|
||||
}
|
||||
|
||||
_MODULE_ICON_COLORS = {
|
||||
"ki": "#0984E3",
|
||||
"kg": "#00B894",
|
||||
"notizen": "#6C5CE7",
|
||||
"translator": "#0078D7",
|
||||
"medwork_chat": "#2D3436",
|
||||
"praxis_chat": "#636E72",
|
||||
}
|
||||
|
||||
_ICON_SZ = 38
|
||||
|
||||
|
||||
def _draw_module_icon(c: tk.Canvas, key: str):
|
||||
"""Draw a minimal white line-art icon (38×38) for the given module."""
|
||||
s = _ICON_SZ
|
||||
m = s // 2
|
||||
fg = "#FFFFFF"
|
||||
|
||||
if key == "ki":
|
||||
c.create_polygon(m, 7, m + 5, m, m, s - 7, m - 5, m,
|
||||
fill=fg, outline="")
|
||||
c.create_polygon(7, m, m, m - 5, s - 7, m, m, m + 5,
|
||||
fill=fg, outline="")
|
||||
|
||||
elif key == "kg":
|
||||
c.create_rectangle(10, 12, s - 10, s - 7, outline=fg, width=2)
|
||||
c.create_rectangle(14, 8, s - 14, 15, outline=fg, width=1.5)
|
||||
c.create_line(m, 18, m, s - 10, fill=fg, width=2.5)
|
||||
c.create_line(14, m + 2, s - 14, m + 2, fill=fg, width=2.5)
|
||||
|
||||
elif key == "notizen":
|
||||
c.create_oval(m - 5, 7, m + 5, 19, outline=fg, width=2)
|
||||
c.create_arc(m - 9, 13, m + 9, 27, start=180, extent=180,
|
||||
outline=fg, width=2, style="arc")
|
||||
c.create_line(m, 27, m, s - 9, fill=fg, width=2)
|
||||
c.create_line(m - 5, s - 9, m + 5, s - 9, fill=fg, width=2)
|
||||
|
||||
elif key == "translator":
|
||||
c.create_rectangle(7, 8, m + 1, s - 10, outline=fg, width=1.5)
|
||||
c.create_rectangle(m - 1, 10, s - 7, s - 8, outline=fg, width=1.5)
|
||||
c.create_text(14, m, text="A",
|
||||
font=("Segoe UI", 9, "bold"), fill=fg)
|
||||
c.create_text(s - 14, m + 1, text="\u6587",
|
||||
font=("Segoe UI", 8), fill=fg)
|
||||
|
||||
elif key == "medwork_chat":
|
||||
nodes = [(m, 9), (9, s - 11), (s - 9, s - 11)]
|
||||
for i in range(3):
|
||||
for j in range(i + 1, 3):
|
||||
c.create_line(*nodes[i], *nodes[j], fill=fg, width=1.5)
|
||||
for x, y in nodes:
|
||||
c.create_oval(x - 4, y - 4, x + 4, y + 4, fill=fg, outline="")
|
||||
|
||||
elif key == "praxis_chat":
|
||||
c.create_oval(8, 7, s - 8, s - 13, outline=fg, width=2)
|
||||
bg = c.cget("bg")
|
||||
c.create_polygon(12, s - 14, 10, s - 7, 18, s - 14,
|
||||
fill=bg, outline=bg)
|
||||
c.create_line(12, s - 14, 10, s - 7, fill=fg, width=2)
|
||||
c.create_line(10, s - 7, 17, s - 14, fill=fg, width=2)
|
||||
|
||||
_GRID_COLS = 2
|
||||
_BAR_H = 6
|
||||
_WIN_W = 620
|
||||
_WIN_MIN_W = 520
|
||||
_WIN_MIN_H = 500
|
||||
|
||||
|
||||
class _Tooltip:
|
||||
"""Dezentes Hover-Tooltip im Glas-Stil."""
|
||||
|
||||
def __init__(self, widget, text: str):
|
||||
self._widget = widget
|
||||
self._text = text
|
||||
self._tip = None
|
||||
widget.bind("<Enter>", self._show, add="+")
|
||||
widget.bind("<Leave>", self._hide, add="+")
|
||||
|
||||
def _show(self, event):
|
||||
if self._tip:
|
||||
return
|
||||
x = self._widget.winfo_rootx() + 20
|
||||
y = self._widget.winfo_rooty() + self._widget.winfo_height() + 4
|
||||
self._tip = tw = tk.Toplevel(self._widget)
|
||||
tw.wm_overrideredirect(True)
|
||||
tw.wm_geometry(f"+{x}+{y}")
|
||||
tw.configure(bg="#2D3436")
|
||||
tk.Label(
|
||||
tw, text=self._text,
|
||||
font=(FONT_FAMILY, 9), fg="#F0F0F0", bg="#2D3436",
|
||||
padx=12, pady=8, justify="left",
|
||||
).pack()
|
||||
|
||||
def _hide(self, event):
|
||||
if self._tip:
|
||||
self._tip.destroy()
|
||||
self._tip = None
|
||||
|
||||
def update_text(self, text: str):
|
||||
self._text = text
|
||||
|
||||
|
||||
class AzaLauncher(tk.Tk):
|
||||
"""Premium-Startseite mit Modulauswahl, KI-Kapazität und verstecktem Admin."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.title("AZA \u2013 Digitales Praxis-Cockpit")
|
||||
self.configure(bg=BG)
|
||||
self.resizable(True, True)
|
||||
self.minsize(_WIN_MIN_W, _WIN_MIN_H)
|
||||
|
||||
self.attributes("-topmost", True)
|
||||
|
||||
self._logo_img = None
|
||||
try:
|
||||
logo_path = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)), "logo.png"
|
||||
)
|
||||
if os.path.exists(logo_path):
|
||||
from PIL import Image, ImageTk
|
||||
img = Image.open(logo_path).resize((44, 44), Image.Resampling.LANCZOS)
|
||||
self._logo_img = ImageTk.PhotoImage(img)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self._selected_module = None
|
||||
prefs = load_launcher_prefs()
|
||||
self._auto_open_var = tk.BooleanVar(value=prefs.get("auto_open", False))
|
||||
|
||||
self._build_ui()
|
||||
self._apply_geometry()
|
||||
self.protocol("WM_DELETE_WINDOW", self._on_close)
|
||||
|
||||
def _apply_geometry(self):
|
||||
saved_geom = load_toplevel_geometry("launcher")
|
||||
if saved_geom:
|
||||
try:
|
||||
self.geometry(saved_geom)
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
self.update_idletasks()
|
||||
req_w = max(self.winfo_reqwidth(), _WIN_W)
|
||||
req_h = self.winfo_reqheight() + 20
|
||||
sw = self.winfo_screenwidth()
|
||||
sh = self.winfo_screenheight()
|
||||
win_w = min(req_w, sw - 40)
|
||||
win_h = min(req_h, sh - 80)
|
||||
self.minsize(_WIN_MIN_W, min(win_h, _WIN_MIN_H))
|
||||
x = (sw - win_w) // 2
|
||||
y = max(20, (sh - win_h) // 2)
|
||||
self.geometry(f"{win_w}x{win_h}+{x}+{y}")
|
||||
|
||||
# ── UI Aufbau ──────────────────────────────────────────────────────────────
|
||||
|
||||
def _build_ui(self):
|
||||
outer = tk.Frame(self, bg=BG)
|
||||
outer.pack(fill="both", expand=True, padx=36, pady=(28, 20))
|
||||
|
||||
# ── Header ──
|
||||
header = tk.Frame(outer, bg=BG)
|
||||
header.pack(fill="x")
|
||||
|
||||
title_row = tk.Frame(header, bg=BG)
|
||||
title_row.pack(fill="x")
|
||||
|
||||
if self._logo_img:
|
||||
logo_lbl = tk.Label(title_row, image=self._logo_img, bg=BG, cursor="hand2")
|
||||
logo_lbl.pack(side="left", padx=(0, 14))
|
||||
logo_lbl.bind("<Double-Button-1>", self._open_admin)
|
||||
|
||||
title_block = tk.Frame(title_row, bg=BG)
|
||||
title_block.pack(side="left", anchor="w")
|
||||
aza_lbl = tk.Label(title_block, text="AZA",
|
||||
font=(FONT_FAMILY, 24, "bold"), fg=ACCENT, bg=BG,
|
||||
cursor="hand2")
|
||||
aza_lbl.pack(anchor="w")
|
||||
aza_lbl.bind("<Double-Button-1>", self._open_admin)
|
||||
tk.Label(title_block, text="Medizinischer KI-Arbeitsplatz",
|
||||
font=(FONT_FAMILY, 10), fg=SUBTLE, bg=BG
|
||||
).pack(anchor="w")
|
||||
|
||||
self._build_capacity_bar(header)
|
||||
|
||||
# ── Separator ──
|
||||
tk.Frame(outer, bg="#E2E8F0", height=1).pack(fill="x", pady=(14, 16))
|
||||
|
||||
# ── Card Grid ──
|
||||
grid = tk.Frame(outer, bg=BG)
|
||||
grid.pack(fill="both", expand=True)
|
||||
|
||||
for c in range(_GRID_COLS):
|
||||
grid.columnconfigure(c, weight=1, uniform="col")
|
||||
num_rows = (len(LAUNCHER_MODULES) + _GRID_COLS - 1) // _GRID_COLS
|
||||
for r in range(num_rows):
|
||||
grid.rowconfigure(r, weight=1)
|
||||
|
||||
for i, mod_key in enumerate(LAUNCHER_MODULES):
|
||||
row, col = divmod(i, _GRID_COLS)
|
||||
card = self._create_card(grid, mod_key)
|
||||
card.grid(row=row, column=col,
|
||||
padx=SPACING // 2, pady=SPACING // 2,
|
||||
sticky="nsew")
|
||||
|
||||
# ── Auto-Open Bereich ──
|
||||
self._build_auto_open_section(outer)
|
||||
|
||||
# ── Footer ──
|
||||
_FOOTER_BG = "#EDF2F7"
|
||||
footer_wrap = tk.Frame(outer, bg=_FOOTER_BG, highlightthickness=0)
|
||||
footer_wrap.pack(fill="x", pady=(16, 0), ipady=8)
|
||||
|
||||
footer = tk.Frame(footer_wrap, bg=_FOOTER_BG)
|
||||
footer.pack(fill="x", padx=12)
|
||||
|
||||
lbl_status = tk.Label(
|
||||
footer, text="Systemstatus",
|
||||
font=(FONT_FAMILY, 9, "bold"), fg=ACCENT, bg=_FOOTER_BG,
|
||||
cursor="hand2",
|
||||
)
|
||||
lbl_status.pack(side="left")
|
||||
lbl_status.bind("<Button-1>", self._open_systemstatus)
|
||||
|
||||
self._build_key_status(footer, _FOOTER_BG)
|
||||
|
||||
try:
|
||||
from aza_version import APP_VERSION
|
||||
tk.Label(
|
||||
footer, text=f"v{APP_VERSION}",
|
||||
font=(FONT_FAMILY, 8), fg="#A0AEC0", bg=_FOOTER_BG,
|
||||
).pack(side="right")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ── Auto-Open Bereich ──────────────────────────────────────────────────────
|
||||
|
||||
def _build_auto_open_section(self, parent):
|
||||
prefs = load_launcher_prefs()
|
||||
default_mod = prefs.get("default_module", "")
|
||||
|
||||
section = tk.Frame(parent, bg=BG)
|
||||
section.pack(fill="x", pady=(14, 0))
|
||||
|
||||
tk.Frame(section, bg="#E2E8F0", height=1).pack(fill="x", pady=(0, 10))
|
||||
|
||||
row1 = tk.Frame(section, bg=BG)
|
||||
row1.pack(fill="x")
|
||||
|
||||
tk.Checkbutton(
|
||||
row1,
|
||||
text="Nächste Auswahl als Standardstart merken",
|
||||
variable=self._auto_open_var,
|
||||
font=(FONT_FAMILY, 9), fg=TEXT, bg=BG,
|
||||
activebackground=BG, selectcolor=CARD_BG,
|
||||
command=self._on_auto_open_toggle,
|
||||
).pack(side="left")
|
||||
|
||||
row2 = tk.Frame(section, bg=BG)
|
||||
row2.pack(fill="x", pady=(4, 0))
|
||||
|
||||
self._auto_open_status = tk.Label(row2, font=(FONT_FAMILY, 9), bg=BG)
|
||||
self._auto_open_status.pack(side="left")
|
||||
|
||||
self._auto_open_reset = tk.Label(
|
||||
row2, text="Zurücksetzen",
|
||||
font=(FONT_FAMILY, 8, "underline"), fg=ACCENT, bg=BG,
|
||||
cursor="hand2",
|
||||
)
|
||||
self._auto_open_reset.bind("<Button-1>", self._reset_auto_open)
|
||||
|
||||
self._auto_open_help = tk.Label(
|
||||
section, font=(FONT_FAMILY, 8), fg=SUBTLE, bg=BG,
|
||||
)
|
||||
self._auto_open_help.pack(anchor="w", pady=(2, 0))
|
||||
|
||||
self._update_auto_open_display(default_mod)
|
||||
|
||||
def _update_auto_open_display(self, mod_key: str):
|
||||
if mod_key and mod_key in LAUNCHER_MODULES:
|
||||
label = LAUNCHER_MODULE_LABELS.get(mod_key, mod_key)
|
||||
self._auto_open_status.configure(
|
||||
text=f"Standardstart: {label}",
|
||||
fg=ACCENT,
|
||||
)
|
||||
self._auto_open_reset.pack(side="left", padx=(10, 0))
|
||||
self._auto_open_help.configure(
|
||||
text="Dieses Modul öffnet sich beim nächsten Programmstart automatisch.",
|
||||
)
|
||||
else:
|
||||
self._auto_open_status.configure(
|
||||
text="Kein Standardstart festgelegt",
|
||||
fg=SUBTLE,
|
||||
)
|
||||
self._auto_open_reset.pack_forget()
|
||||
self._auto_open_help.configure(
|
||||
text="Aktivieren Sie die Option und wählen Sie ein Modul, um einen Standardstart festzulegen.",
|
||||
)
|
||||
|
||||
def _on_auto_open_toggle(self):
|
||||
if not self._auto_open_var.get():
|
||||
save_launcher_prefs("", False)
|
||||
self._update_auto_open_display("")
|
||||
|
||||
def _reset_auto_open(self, event=None):
|
||||
self._auto_open_var.set(False)
|
||||
save_launcher_prefs("", False)
|
||||
self._update_auto_open_display("")
|
||||
|
||||
# ── Admin-Zugang ──────────────────────────────────────────────────────────
|
||||
|
||||
def _open_systemstatus(self, event=None):
|
||||
try:
|
||||
from aza_systemstatus import show_systemstatus
|
||||
show_systemstatus(self)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _open_admin(self, event=None):
|
||||
try:
|
||||
from aza_admin import show_admin_login, show_admin_panel
|
||||
if show_admin_login(self):
|
||||
show_admin_panel(self)
|
||||
self._refresh_capacity()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _refresh_capacity(self):
|
||||
"""Aktualisiert Kapazitätsanzeige nach Admin-Aktion."""
|
||||
try:
|
||||
remaining = get_remaining_tokens()
|
||||
pct = get_capacity_fraction()
|
||||
low = is_capacity_low()
|
||||
est = estimated_reports_remaining()
|
||||
|
||||
self._cap_label.configure(
|
||||
text=f"Ihre KI-Kapazität: {format_number_de(remaining)} Einheiten verbleibend",
|
||||
fg=WARNING_AMBER if low else SUBTLE,
|
||||
)
|
||||
self._cap_canvas.delete("all")
|
||||
self._cap_canvas.update_idletasks()
|
||||
self._draw_gradient_bar(self._cap_canvas, pct, _BAR_H)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ── KI-Kapazitätsanzeige ──────────────────────────────────────────────────
|
||||
|
||||
def _build_capacity_bar(self, parent):
|
||||
frame = tk.Frame(parent, bg=BG)
|
||||
frame.pack(fill="x", pady=(12, 0))
|
||||
|
||||
remaining = get_remaining_tokens()
|
||||
pct = get_capacity_fraction()
|
||||
low = is_capacity_low()
|
||||
est = estimated_reports_remaining()
|
||||
|
||||
label_text = f"KI-Kapazität: {format_number_de(remaining)} Einheiten"
|
||||
color = WARNING_AMBER if low else "#A0AEC0"
|
||||
self._cap_label = tk.Label(
|
||||
frame, text=label_text,
|
||||
font=(FONT_FAMILY, 8), fg=color, bg=BG, anchor="e",
|
||||
)
|
||||
self._cap_label.pack(anchor="e", pady=(0, 3))
|
||||
|
||||
self._cap_canvas = canvas = tk.Canvas(
|
||||
frame, height=_BAR_H, bg=BG, highlightthickness=0,
|
||||
)
|
||||
canvas.pack(fill="x")
|
||||
canvas.bind("<Configure>", lambda e: self._draw_gradient_bar(canvas, pct, _BAR_H))
|
||||
|
||||
tooltip_text = (
|
||||
f"Entspricht ca. {est} weiteren Berichten\n"
|
||||
"(Basierend auf Ihrem Durchschnittsverbrauch)"
|
||||
)
|
||||
if low:
|
||||
tooltip_text += (
|
||||
"\n\n\u26A0 Kapazität fast aufgebraucht.\n"
|
||||
"Guthaben unter aza-medwork.ch nachfüllen."
|
||||
)
|
||||
_Tooltip(canvas, tooltip_text)
|
||||
_Tooltip(self._cap_label, tooltip_text)
|
||||
|
||||
@staticmethod
|
||||
def _draw_gradient_bar(canvas, pct: float, bar_h: int):
|
||||
canvas.delete("all")
|
||||
w = canvas.winfo_width()
|
||||
if w <= 1:
|
||||
return
|
||||
filled_w = max(0, min(w, int(w * pct)))
|
||||
|
||||
canvas.create_rectangle(0, 0, w, bar_h, fill="#EDF2F7", outline="")
|
||||
|
||||
if filled_w <= 0:
|
||||
return
|
||||
|
||||
if pct <= 0.02:
|
||||
canvas.create_rectangle(0, 0, filled_w, bar_h, fill=DANGER, outline="")
|
||||
elif pct <= 0.10:
|
||||
canvas.create_rectangle(0, 0, filled_w, bar_h, fill=WARNING_AMBER, outline="")
|
||||
else:
|
||||
r1, g1, b1 = 0x00, 0x78, 0xD7
|
||||
r2, g2, b2 = 0x00, 0xCE, 0xC9
|
||||
steps = min(filled_w, 120)
|
||||
step_w = filled_w / steps
|
||||
for i in range(steps):
|
||||
t = i / max(1, steps - 1)
|
||||
r = int(r1 + (r2 - r1) * t)
|
||||
g = int(g1 + (g2 - g1) * t)
|
||||
b = int(b1 + (b2 - b1) * t)
|
||||
x1 = int(i * step_w)
|
||||
x2 = int((i + 1) * step_w)
|
||||
canvas.create_rectangle(
|
||||
x1, 0, x2, bar_h,
|
||||
fill=f"#{r:02x}{g:02x}{b:02x}", outline="",
|
||||
)
|
||||
|
||||
# ── Key-Status ────────────────────────────────────────────────────────────
|
||||
|
||||
def _build_key_status(self, parent, bg_color=None):
|
||||
bg = bg_color or BG
|
||||
try:
|
||||
from security_vault import has_vault_key, get_masked_key
|
||||
if has_vault_key():
|
||||
masked = get_masked_key()
|
||||
tk.Label(
|
||||
parent,
|
||||
text=f"Schlüssel aktiv ({masked})",
|
||||
font=(FONT_FAMILY, 8), fg="#00B894", bg=bg,
|
||||
).pack(side="left", padx=(12, 0))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ── Card Rendering ────────────────────────────────────────────────────────
|
||||
|
||||
def _create_card(self, parent, mod_key: str) -> tk.Frame:
|
||||
label = LAUNCHER_MODULE_LABELS.get(mod_key, mod_key)
|
||||
desc = _MODULE_DESCRIPTIONS.get(mod_key, "")
|
||||
icon_color = _MODULE_ICON_COLORS.get(mod_key, ACCENT)
|
||||
|
||||
card = tk.Frame(parent, bg=CARD_BG, cursor="hand2",
|
||||
highlightthickness=1, highlightbackground=CARD_BORDER)
|
||||
|
||||
inner = tk.Frame(card, bg=CARD_BG, cursor="hand2")
|
||||
inner.pack(fill="both", expand=True, padx=20, pady=18)
|
||||
|
||||
top_row = tk.Frame(inner, bg=CARD_BG, cursor="hand2")
|
||||
top_row.pack(fill="x", pady=(0, 10))
|
||||
|
||||
icon_cv = tk.Canvas(top_row, width=_ICON_SZ, height=_ICON_SZ,
|
||||
bg=icon_color, highlightthickness=0, cursor="hand2")
|
||||
icon_cv.pack(side="left")
|
||||
_draw_module_icon(icon_cv, mod_key)
|
||||
|
||||
tk.Label(inner, text=label, font=(FONT_FAMILY, 12, "bold"),
|
||||
fg=TEXT, bg=CARD_BG, anchor="w", cursor="hand2"
|
||||
).pack(anchor="w")
|
||||
|
||||
if desc:
|
||||
tk.Label(inner, text=desc, font=(FONT_FAMILY, 9),
|
||||
fg=SUBTLE, bg=CARD_BG, anchor="w",
|
||||
justify="left", cursor="hand2"
|
||||
).pack(anchor="w", pady=(4, 0))
|
||||
|
||||
def on_enter(e):
|
||||
card.configure(highlightbackground=CARD_HOVER_BORDER, highlightthickness=2)
|
||||
for w in _deep_children(card):
|
||||
try:
|
||||
if isinstance(w, tk.Canvas):
|
||||
continue
|
||||
w.configure(bg=CARD_HOVER_BG)
|
||||
except tk.TclError:
|
||||
pass
|
||||
|
||||
def on_leave(e):
|
||||
card.configure(highlightbackground=CARD_BORDER, highlightthickness=1)
|
||||
for w in _deep_children(card):
|
||||
try:
|
||||
if isinstance(w, tk.Canvas):
|
||||
continue
|
||||
w.configure(bg=CARD_BG)
|
||||
except tk.TclError:
|
||||
pass
|
||||
|
||||
def on_click(e, key=mod_key):
|
||||
self._select(key)
|
||||
|
||||
for w in _deep_children(card):
|
||||
w.bind("<Enter>", on_enter)
|
||||
w.bind("<Leave>", on_leave)
|
||||
w.bind("<Button-1>", on_click)
|
||||
|
||||
return card
|
||||
|
||||
# ── Modul-Auswahl / Schliessen ────────────────────────────────────────────
|
||||
|
||||
def _save_geom(self):
|
||||
try:
|
||||
save_toplevel_geometry("launcher", self.geometry())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _select(self, mod_key: str):
|
||||
self._selected_module = mod_key
|
||||
if self._auto_open_var.get():
|
||||
save_launcher_prefs(mod_key, True)
|
||||
else:
|
||||
save_launcher_prefs("", False)
|
||||
self._save_geom()
|
||||
self.destroy()
|
||||
|
||||
def _on_close(self):
|
||||
self._selected_module = None
|
||||
self._save_geom()
|
||||
self.destroy()
|
||||
|
||||
def run(self) -> str | None:
|
||||
self.mainloop()
|
||||
return self._selected_module
|
||||
|
||||
|
||||
def _deep_children(widget):
|
||||
"""Widget + alle verschachtelten Kinder."""
|
||||
result = [widget]
|
||||
for child in widget.winfo_children():
|
||||
result.extend(_deep_children(child))
|
||||
return result
|
||||
|
||||
|
||||
def should_skip_launcher() -> tuple[bool, str]:
|
||||
"""Prüft ob der Launcher übersprungen werden soll."""
|
||||
prefs = load_launcher_prefs()
|
||||
mod = prefs.get("default_module", "")
|
||||
auto = prefs.get("auto_open", False)
|
||||
if auto and mod and mod in LAUNCHER_MODULES:
|
||||
return True, mod
|
||||
return False, ""
|
||||
Reference in New Issue
Block a user