feat: initialize CCMA member administration

This commit is contained in:
Marcel Peterkau
2026-06-21 16:46:15 +02:00
parent 4c6a1191ee
commit dfd5b1192b
184 changed files with 5051 additions and 0 deletions
+134
View File
@@ -0,0 +1,134 @@
from __future__ import annotations
import tkinter as tk
from pathlib import Path
from tkinter import filedialog, messagebox
from ccma import __version__
from ccma.config import load_config
from ccma.domain.dates import setup_system_locale
from ccma.storage.repository import MemberRepository
from ccma.ui.main_window import MainWindow
from ccma.ui.monitors import (
centered_geometry,
ensure_visible_geometry,
monitor_for_geometry,
preferred_monitor,
)
from ccma.ui.splash import SplashScreen, StartupResult
from ccma.ui.theme import load_theme
from ccma.ui.window_state import is_maximized, maximize
class CCMAApp(tk.Tk):
def __init__(self):
super().__init__()
setup_system_locale()
self.config_obj = load_config()
self.title(f"CCMA · v{__version__}")
self.minsize(1050, 650)
load_theme(self, self.config_obj.theme_mode)
self.startup_monitor = preferred_monitor(self, self.config_obj.monitor_bounds)
initial_geometry = self.config_obj.window_geometry or centered_geometry(
1500, 860, self.startup_monitor
)
self.geometry(ensure_visible_geometry(initial_geometry, self.startup_monitor))
self._last_normal_geometry = self.geometry()
self._geometry_capture_job: str | None = None
self.bind("<Configure>", self._remember_window_geometry)
self.protocol("WM_DELETE_WINDOW", self.close)
self.withdraw()
self.after_idle(self._start)
def _start(self) -> None:
store_path = self._resolve_store()
if not store_path:
self.destroy()
return
repository = MemberRepository(store_path)
try:
SplashScreen(
self,
repository,
self._startup_complete,
self._startup_failed,
run_housekeeper=self.config_obj.run_housekeeper_on_startup,
monitor=self.startup_monitor,
housekeeper_settings=self.config_obj.housekeeper_settings(),
)
except Exception as exc:
self._startup_failed(exc)
def _resolve_store(self) -> Path | None:
configured = self.config_obj.store_path.strip()
if configured:
return Path(configured).expanduser()
selected = filedialog.askdirectory(
parent=self,
title="Zentrales CCMA-Mitgliederverzeichnis auswählen oder anlegen",
initialdir=str(Path.home()),
mustexist=False,
)
if not selected:
return None
self.config_obj.store_path = selected
self.config_obj.save()
return Path(selected)
def _startup_complete(self, result: StartupResult) -> None:
self.deiconify()
main = MainWindow(
self,
result.repository,
self.config_obj,
result.findings,
result.validation_errors,
)
main.pack(fill="both", expand=True)
if self.config_obj.window_state in {"maximized", "zoomed"}:
self.after_idle(lambda: maximize(self))
def _startup_failed(self, error: Exception) -> None:
self.deiconify()
messagebox.showerror("CCMA konnte nicht gestartet werden", str(error), parent=self)
self.destroy()
def _remember_window_geometry(self, _event: tk.Event | None = None) -> None:
if self._geometry_capture_job:
try:
self.after_cancel(self._geometry_capture_job)
except tk.TclError:
pass
self._geometry_capture_job = self.after(150, self._capture_normal_geometry)
def _capture_normal_geometry(self) -> None:
self._geometry_capture_job = None
try:
if self.state() == "normal" and not is_maximized(self) and self.winfo_viewable():
self._last_normal_geometry = self.winfo_geometry()
except tk.TclError:
return
def close(self) -> None:
try:
maximized = is_maximized(self)
current_geometry = self.winfo_geometry()
if not maximized:
self._last_normal_geometry = current_geometry
self.config_obj.window_geometry = self._last_normal_geometry
self.config_obj.window_state = "maximized" if maximized else "normal"
monitor = monitor_for_geometry(self, current_geometry)
self.config_obj.monitor_bounds = monitor.as_tuple()
self.config_obj.save()
except (OSError, tk.TclError):
pass
self.destroy()
def main() -> None:
app = CCMAApp()
app.mainloop()
if __name__ == "__main__":
main()