from __future__ import annotations import json import tkinter as tk from pathlib import Path from tkinter import ttk from typing import Any from ccma import __version__ from ccma.ui.icons import IconStore CHANGELOG_PATH = Path(__file__).resolve().parent.parent / "assets" / "CHANGELOG.json" def load_changelog(path: Path = CHANGELOG_PATH) -> list[dict[str, Any]]: try: data = json.loads(path.read_text(encoding="utf-8")) except (OSError, ValueError, TypeError): return [] if not isinstance(data, list): return [] return [entry for entry in data if isinstance(entry, dict)] class ChangelogView(ttk.Frame): def __init__(self, master: tk.Misc): super().__init__(master, padding=14) self.icons = IconStore(self) self.entries = load_changelog() self._build_ui() def _build_ui(self) -> None: self.columnconfigure(0, weight=1) self.rowconfigure(1, weight=1) header = ttk.Frame(self) header.grid(row=0, column=0, sticky="ew", pady=(0, 14)) header.columnconfigure(1, weight=1) history_icon = self.icons.get("history", 28) icon_label = ttk.Label(header, image=history_icon) icon_label.image = history_icon icon_label.grid(row=0, column=0, rowspan=2, padx=(0, 10)) ttk.Label(header, text="Änderungsverlauf", style="TabTitle.TLabel").grid(row=0, column=1, sticky="w") ttk.Label( header, text=f"Aktuelle Version {__version__} · {len(self.entries)} Release-Einträge", style="Mono.TLabel", ).grid(row=1, column=1, sticky="w", pady=(2, 0)) container = ttk.Frame(self) container.grid(row=1, column=0, sticky="nsew") container.columnconfigure(0, weight=1) container.rowconfigure(0, weight=1) background = ttk.Style(self).lookup("TFrame", "background") or "#ffffff" self.canvas = tk.Canvas(container, highlightthickness=0, borderwidth=0, background=background) scrollbar = ttk.Scrollbar(container, orient="vertical", command=self.canvas.yview) self.canvas.configure(yscrollcommand=scrollbar.set) self.canvas.grid(row=0, column=0, sticky="nsew") scrollbar.grid(row=0, column=1, sticky="ns") self.content = ttk.Frame(self.canvas) self.content.columnconfigure(0, weight=1) self.content_window = self.canvas.create_window((0, 0), anchor="nw", window=self.content) self.content.bind("", self._update_scroll_region) self.canvas.bind("", self._resize_content) self._render_entries() self._bind_mousewheel_tree(self.canvas) self._bind_mousewheel_tree(self.content) def _render_entries(self) -> None: if not self.entries: ttk.Label(self.content, text="Kein Changelog verfügbar.", style="Muted.TLabel").grid( row=0, column=0, sticky="w", padx=8, pady=8 ) return for row, entry in enumerate(self.entries): version = str(entry.get("version", "unbekannt")) card = ttk.LabelFrame(self.content, padding=14) card.grid(row=row, column=0, sticky="ew", padx=(0, 8), pady=(0, 12)) card.columnconfigure(1, weight=1) ttk.Label(card, text=f"VERSION {version}", style="TimelineHeader.TLabel").grid( row=0, column=0, sticky="w" ) if version == __version__: ttk.Label(card, text="CURRENT", style="Mono.TLabel").grid( row=0, column=1, sticky="w", padx=(12, 0) ) ttk.Label(card, text=str(entry.get("date", "")), style="Muted.TLabel").grid( row=0, column=2, sticky="e" ) changes = entry.get("changes", []) if not isinstance(changes, list): continue for change_row, change in enumerate(changes, start=1): ttk.Label(card, text="•", style="TimelineHeader.TLabel").grid( row=change_row, column=0, sticky="nw", pady=(7, 0) ) ttk.Label( card, text=str(change), justify="left", wraplength=650, ).grid(row=change_row, column=1, columnspan=2, sticky="w", padx=(8, 0), pady=(7, 0)) def _update_scroll_region(self, _event: tk.Event | None = None) -> None: self.canvas.configure(scrollregion=self.canvas.bbox("all")) def _resize_content(self, event: tk.Event) -> None: self.canvas.itemconfigure(self.content_window, width=event.width) def _bind_mousewheel_tree(self, widget: tk.Misc) -> None: widget.bind("", self._on_mousewheel, add="+") widget.bind("", self._on_mousewheel, add="+") widget.bind("", self._on_mousewheel, add="+") for child in widget.winfo_children(): self._bind_mousewheel_tree(child) def _on_mousewheel(self, event: tk.Event) -> None: if getattr(event, "num", None) == 4: delta = -1 elif getattr(event, "num", None) == 5: delta = 1 else: delta = -1 if getattr(event, "delta", 0) > 0 else 1 self.canvas.yview_scroll(delta * 3, "units")