Source code for repoman.ui.wizard.select_page

from __future__ import annotations

import json
from pathlib import Path

import gi

gi.require_version("Adw", "1")
gi.require_version("Gtk", "4.0")
from gi.repository import Adw, Gio, GLib, Gtk

from ... import config_io
from ...models import AvailabilityStatus, Repository, WizardState
from ..position import center_on_parent
from .base_page import RepomanWizardPage


[docs] class SelectReposPage(RepomanWizardPage): """ Step 1 — user selects which repos to re-enable. Pre-ticks everything except confirmed UNAVAILABLE. Unavailable repos stay visible and tickable — user may know better. """ __gtype_name__ = "RepomanSelectReposPage" def __init__(self, state: WizardState, nav_view: Adw.NavigationView, **kwargs) -> None: super().__init__( state=state, nav_view=nav_view, title="Select repositories", tag="select", next_label="Check availability", **kwargs, ) self._checks: dict[int, tuple[Repository, Gtk.CheckButton]] = {} self._build_ui() self.refresh_proceed()
[docs] def can_proceed(self) -> bool: return any(cb.get_active() for _, cb in self._checks.values())
def _on_check_toggled(self) -> None: self._update_select_all_btn() self.refresh_proceed() def _update_select_all_btn(self) -> None: all_checked = all(cb.get_active() for _, cb in self._checks.values()) self._select_all_label.set_label("Deselect all" if all_checked else "Select all") def _on_select_all(self, _btn: Gtk.Button) -> None: all_checked = all(cb.get_active() for _, cb in self._checks.values()) for _, cb in self._checks.values(): cb.set_active(not all_checked) self._update_select_all_btn() self.refresh_proceed() def _on_load_from_file(self, _btn: Gtk.Button) -> None: dialog = Gtk.FileDialog.new() dialog.set_title("Load selection from state file") f = Gtk.FileFilter() f.add_pattern("*.repoman") f.set_name("Repoman configs (*.repoman)") store = Gio.ListStore.new(Gtk.FileFilter) store.append(f) dialog.set_filters(store) dialog.open(self.get_root(), None, self._on_file_chosen) def _on_file_chosen(self, dialog: Gtk.FileDialog, result: Gio.AsyncResult) -> None: try: gfile = dialog.open_finish(result) except GLib.Error: return path = Path(gfile.get_path()) try: saved = config_io.load_config(path) except (json.JSONDecodeError, ValueError, KeyError) as exc: dlg = Gtk.Window( title="Could not load file", transient_for=self.get_root(), modal=True, resizable=False, default_width=360, ) err_box = Gtk.Box( orientation=Gtk.Orientation.VERTICAL, spacing=12, margin_top=18, margin_bottom=18, margin_start=18, margin_end=18, ) err_box.append(Gtk.Label(label=str(exc), wrap=True, xalign=0, max_width_chars=42)) btn_row = Gtk.Box(halign=Gtk.Align.END, margin_top=6) ok_btn = Gtk.Button(label="OK") ok_btn.connect("clicked", lambda _: dlg.close()) btn_row.append(ok_btn) err_box.append(btn_row) dlg.set_child(err_box) center_on_parent(dlg) dlg.present() return matched, _ = config_io.match_repos(saved, list(self._state.candidate_repos)) enabled_uris = {entry["uris"][0] for entry, _ in matched if entry.get("enabled") and entry.get("uris")} for repo, cb in self._checks.values(): cb.set_active(bool(repo.uris) and repo.uris[0] in enabled_uris) self._update_select_all_btn() self.refresh_proceed() def _on_proceed(self) -> None: self._state.selected = [repo for repo, cb in self._checks.values() if cb.get_active()] from .check_page import CheckAvailabilityPage self._nav_view.push(CheckAvailabilityPage(state=self._state, nav_view=self._nav_view)) def _build_ui(self) -> None: subtitle = Gtk.Label( label=(f"{len(self._state.candidate_repos)} repositories need review for {self._state.target_codename}"), wrap=True, xalign=0, ) subtitle.set_opacity(0.55) self._content_box.append(subtitle) self._load_btn = Gtk.Button(label="Select from state file…", valign=Gtk.Align.CENTER) self._load_btn.add_css_class("flat") self._load_btn.connect("clicked", self._on_load_from_file) # Fixed-width label so the button never shrinks when text toggles between # "Select all" (10 chars) and "Deselect all" (12 chars), keeping the load # button anchored in place. self._select_all_label = Gtk.Label(width_chars=12) self._select_all_btn = Gtk.Button(child=self._select_all_label, valign=Gtk.Align.CENTER) self._select_all_btn.add_css_class("flat") self._select_all_btn.connect("clicked", self._on_select_all) header_box = Gtk.Box(valign=Gtk.Align.CENTER) header_box.append(self._load_btn) header_box.append(Gtk.Box(hexpand=True)) header_box.append(self._select_all_btn) self._content_box.append(header_box) group = Adw.PreferencesGroup() self._content_box.append(group) for repo in self._state.candidate_repos: row = Adw.ActionRow( title=repo.display_name, subtitle=repo.uris[0] if repo.uris else "", ) check = Gtk.CheckButton(active=(repo.availability != AvailabilityStatus.UNAVAILABLE)) check.connect("toggled", lambda _cb: self._on_check_toggled()) row.add_prefix(check) row.set_activatable_widget(check) row.add_suffix(self._status_icon(repo)) group.add(row) self._checks[id(repo)] = (repo, check) self._update_select_all_btn() @staticmethod def _status_icon(repo: Repository) -> Gtk.Widget: icon_name, css = { AvailabilityStatus.AVAILABLE: ("emblem-ok-symbolic", "success"), AvailabilityStatus.UNAVAILABLE: ("dialog-warning-symbolic", "warning"), AvailabilityStatus.SUITE_AGNOSTIC: ("emblem-synchronizing-symbolic", ""), }.get(repo.availability, ("dialog-question-symbolic", "")) icon = Gtk.Image.new_from_icon_name(icon_name) if css: icon.add_css_class(css) if repo.availability not in ( AvailabilityStatus.AVAILABLE, AvailabilityStatus.UNAVAILABLE, AvailabilityStatus.SUITE_AGNOSTIC, ): icon.set_opacity(0.55) return icon