mirror of
https://github.com/kuhyx/diet-guard.git
synced 2026-11-10 19:07:15 +01:00
Sixth batch of the 250-line sweep. All six gate sources named for this batch are now under cap, plus _gate_layout_probe.py, which this batch's import rewiring pulled into the changeset (over-cap count 38 -> 31). _gatelock_ui.py 477 -> 174 (palette/leaf-helper hub) _gate_layout_probe.py 346 -> 196 _gatelock_groups.py 308 -> 137 _gatelock_calendar_ui 295 -> 77 _gatelock_calendar.py 286 -> 210 _gatelock.py 263 -> 238 _gatelock_mealflow.py 259 -> 207 New siblings: _gatelock_fields (input widgets), _gatelock_layout (assembly), _gatelock_widgetgroups (per-widget mirroring), _gatelock_calendar_types (bundles/palette), _gatelock_calendar_widgets (History builders), _gatelock_dashboard and _gatelock_budgetedit (mixins), _gatelock_lockfile (the stdlib-only flock), _gate_focus_probe and _gate_measurement. The load-bearing part is the test seam, not the line counts. `_gate_fixtures._GATE_TK_MODULES` patches `tk` on a hand-listed tuple, and that list fails OPEN: a widget builder split into a new sibling that nobody adds to it builds *real* Tk widgets against fake parents. Neither existing detector sees that -- check_patch_targets.py only validates paths that still resolve, and the ~8s runtime only catches network calls. It bit twice during this split (real StringVars from make_calendar_vars returned MagicMocks instead of the asserted text), so test_gate_tk_modules.py now asserts the invariant directly: every module binding `tk` is in the patch set or named in _NO_WIDGETS with a reason. It caught a pre-existing gap too -- _gatelock_support was never in the set. Same fail-open shape drove two other choices: `escape_tab_trap` is a call-time indirection so the keyboard test's patch of `_escape_text_tab_trap` still bites, and the flock pair moved with its conftest patch target updated in the same edit (that one fails closed). build_layout is imported from _gatelock_layout directly rather than re-exported lazily, which avoids both a cycle and an import-outside-toplevel suppression. Verified: 703 Python tests / 100% branch coverage, the 5 layout-fits tests under xvfb, 555 Flutter tests (unchanged), flutter analyze clean, check_patch_targets 0, `sync` 0 (403 entries), pre-commit clean, and `gate --demo` renders both tabs with live dashboard data. ~/.local/share/diet_guard byte-identical across the batch (gate timer paused, restored after). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RA9ynjqXPnexRQnR5oAuCC
138 lines
4.5 KiB
Python
138 lines
4.5 KiB
Python
"""Presenting the gate's N per-monitor copies as if they were one form.
|
|
|
|
Since gatelock v0.2.0 the lock builds one window per live output, so the gate
|
|
is laid out once per monitor and the controller must drive all of them at
|
|
once. The individual per-widget groups -- and the rationale for which kinds of
|
|
state can share a Tk variable and which genuinely diverge -- live in
|
|
:mod:`._gatelock_widgetgroups`; this half assembles them into the single
|
|
``GateWidgetsGroup`` the controller drives, so each file stays under the
|
|
repo's 250-line limit.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
from diet_guard._gatelock_widgetgroups import (
|
|
EntryGroup,
|
|
ListboxGroup,
|
|
TextGroup,
|
|
WidgetGroup,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
import tkinter as tk
|
|
|
|
from diet_guard._gatelock_ui_types import GateVars, GateWidgets
|
|
|
|
__all__ = [
|
|
"EntryGroup",
|
|
"GateWidgetsGroup",
|
|
"ListboxGroup",
|
|
"MacroGroup",
|
|
"TextGroup",
|
|
"WidgetGroup",
|
|
]
|
|
|
|
|
|
class MacroGroup:
|
|
"""The four macro entries, each mirrored onto every monitor."""
|
|
|
|
def __init__(
|
|
self,
|
|
kcal: EntryGroup,
|
|
protein: EntryGroup,
|
|
carbs: EntryGroup,
|
|
fat: EntryGroup,
|
|
) -> None:
|
|
"""Bundle the four macro entry groups."""
|
|
self.kcal = kcal
|
|
self.protein = protein
|
|
self.carbs = carbs
|
|
self.fat = fat
|
|
|
|
|
|
class GateWidgetsGroup:
|
|
"""Every per-monitor copy of the gate, driven as if it were one form.
|
|
|
|
``_build_tabs`` produces one ``GateWidgets`` per live output and they are
|
|
added here; the controller then drives this exactly as it drove a single
|
|
bundle before per-output locking existed.
|
|
"""
|
|
|
|
def __init__(self, vars_: GateVars) -> None:
|
|
"""Start empty; surfaces are added as outputs come up."""
|
|
self._vars = vars_
|
|
self._bundles: list[GateWidgets] = []
|
|
self._outputs: list[str] = []
|
|
|
|
def add(self, widgets: GateWidgets, output_name: str) -> None:
|
|
"""Register the bundle built for one newly-live output."""
|
|
self._bundles.append(widgets)
|
|
self._outputs.append(output_name)
|
|
|
|
def discard(self, output_name: str) -> None:
|
|
"""Forget the bundle for an output that went dark."""
|
|
kept = [
|
|
(bundle, name)
|
|
for bundle, name in zip(self._bundles, self._outputs, strict=True)
|
|
if name != output_name
|
|
]
|
|
self._bundles = [bundle for bundle, _ in kept]
|
|
self._outputs = [name for _, name in kept]
|
|
|
|
@property
|
|
def bundles(self) -> list[GateWidgets]:
|
|
"""Every per-monitor bundle; empty until the lock builds them."""
|
|
return list(self._bundles)
|
|
|
|
def _each(self, name: str) -> list[tk.Misc]:
|
|
"""Collect one named widget from every monitor's bundle."""
|
|
return [getattr(bundle, name) for bundle in self._bundles]
|
|
|
|
@property
|
|
def frame(self) -> WidgetGroup[tk.Misc]:
|
|
"""The gate's outer frame on every monitor."""
|
|
return WidgetGroup(self._each("frame"))
|
|
|
|
@property
|
|
def desc_text(self) -> TextGroup:
|
|
"""The description box on every monitor."""
|
|
return TextGroup(self._each("desc_text"))
|
|
|
|
@property
|
|
def suggestion_box(self) -> ListboxGroup:
|
|
"""The suggestion picker on every monitor."""
|
|
return ListboxGroup(self._each("suggestion_box"))
|
|
|
|
@property
|
|
def status_label(self) -> WidgetGroup[tk.Misc]:
|
|
"""The status line on every monitor."""
|
|
return WidgetGroup(self._each("status_label"))
|
|
|
|
@property
|
|
def basis_prefix(self) -> WidgetGroup[tk.Misc]:
|
|
"""The per-basis prefix label on every monitor."""
|
|
return WidgetGroup(self._each("basis_prefix"))
|
|
|
|
@property
|
|
def amount_entry(self) -> EntryGroup:
|
|
"""The amount entry, over its shared variable."""
|
|
return EntryGroup(self._each("amount_entry"), self._vars.entries.amount)
|
|
|
|
@property
|
|
def per_entry(self) -> EntryGroup:
|
|
"""The per-basis entry, over its shared variable."""
|
|
return EntryGroup(self._each("per_entry"), self._vars.entries.per)
|
|
|
|
@property
|
|
def macros(self) -> MacroGroup:
|
|
"""The four macro entries, each over its shared variable."""
|
|
cells = [bundle.macros for bundle in self._bundles]
|
|
return MacroGroup(
|
|
kcal=EntryGroup([c.kcal for c in cells], self._vars.entries.kcal),
|
|
protein=EntryGroup([c.protein for c in cells], self._vars.entries.protein),
|
|
carbs=EntryGroup([c.carbs for c in cells], self._vars.entries.carbs),
|
|
fat=EntryGroup([c.fat for c in cells], self._vars.entries.fat),
|
|
)
|