Files
dzgui/dzgui/views/pages/changelog.py
T
2026-08-20 13:53:06 +09:00

133 lines
4.6 KiB
Python

import logging
import re
import textwrap
from typing import TYPE_CHECKING
from importlib import resources
from dzgui.const.constants import APP_NAME, APP_NAME_LOWER, CHANGELOG_PATH
from dzgui.strings import changelog as clog
from dzgui.util.strings import missing_changelog
from dzgui.util.format import format_pango
from dzgui.views.mixins.help_menu_mixin import HelpMenuMixin
from dzgui.views.mixins.scrollable_mixin import ScrollableMixin
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk # noqa E402
logger = logging.getLogger(APP_NAME)
if TYPE_CHECKING:
from dzgui.controllers.mc import Controller
class Changelog(HelpMenuMixin, ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
def __init__(self, controller: "Controller"):
super().__init__(propagate_natural_width=False)
try:
changelog = resources.read_text(APP_NAME_LOWER, CHANGELOG_PATH)
except OSError as e:
logger.critical(e)
changelog = missing_changelog
self.controller = controller
self.box = Gtk.Box(
orientation=Gtk.Orientation.VERTICAL, spacing=5, margin_top=10
)
self.add(self.box)
expand_all = Gtk.Button(
label=clog.expand_all, halign=Gtk.Align.START, margin_start=20
)
expand_all.connect("clicked", self._on_expand_all_clicked)
self.box.add(expand_all)
self.expanded = False
self.expanders: list[Gtk.Expander] = []
self.connect("key-press-event", self._on_keypress)
self.connect("key-press-event", self._on_esc_keypress)
changes = self._parse(changelog)
self._generate_nodes(changes)
self.show_all()
def _on_expand_all_clicked(self, button: Gtk.Button) -> None:
self.expanded = not self.expanded
for expander in self.expanders:
# NOTE: simply setting set_expanded() does not trigger activate() signal,
# so margins are not applied
if expander.get_expanded() == self.expanded:
continue
expander.activate()
label = clog.collapse_all if self.expanded else clog.expand_all
button.set_label(label)
def grab_content_area(self) -> None:
self.grab_focus()
def _parse_version(self, version: str) -> tuple[str, str]:
pattern = r"(^##\s\[)(.+)(\] )(.+$)"
match = re.match(pattern, version)
if match:
version = match.group(2)
date = match.group(4)
return version, date
raise ValueError("Changelog parse error")
def _generate_nodes(self, releases: list[tuple[str, list[str]]]) -> None:
for header, changes in releases:
for ind in (0, -1):
if changes[ind] == "":
del changes[ind]
text = "\n".join(changes)
formatted = format_pango(text)
container = Gtk.Box(valign=Gtk.Align.START, halign=Gtk.Align.START)
label = Gtk.Label()
label.set_markup(formatted)
container.add(label)
version, date = self._parse_version(header)
button_text = f"{version} ({date})"
button = Gtk.Button(label=button_text, margin_start=5)
expander = Gtk.Expander()
expander.set_label_widget(button)
expander.add(container)
expander.connect("activate", self._on_expand, container)
self.box.add(expander)
self.expanders.append(expander)
def _on_expand(self, expander: Gtk.Box, container: Gtk.Box) -> None:
"""
Wayland/some themes do not expect widgets to have a size allocation until expanded.
Trying to set margins a priori may cause negative allocation,
as the margins are subtracted from zero. In effect, the widget is not realized
until it is expanded for the first time.
"""
container.set_margin_start(15)
container.set_margin_top(15)
container.set_margin_bottom(15)
def _parse(self, changelog: str) -> list[tuple[str, list[str]]]:
release = ""
releases: list[tuple[str, list[str]]] = []
release_notes: list[str] = []
for line in changelog.splitlines():
if line.startswith("# "):
continue
if line.startswith("## "):
if release != "":
releases.append((release, release_notes))
release_notes = []
release = ""
release = line
continue
release_notes.append(textwrap.fill(line.rstrip(), width=120))
return releases