mirror of
https://github.com/kuhyx/diet-guard.git
synced 2026-11-11 14:48:45 +01:00
The second edit surface, so the schedule is editable from either device the way the budget already is. A row on the History tab beside the budget row, with the same Edit/Save toggle: first hour, last hour, meals per day, and the derived checkpoint times underneath so the effect of a change is visible before it is saved. Validated rather than clamped. MealSchedule.normalized() would happily turn "20 to 8" into a two-meal day ending at 09:00, which is a sensible defence against a peer's corrupt sync data and a terrible response to a typo at the keyboard. Each rejection says what is wrong and leaves the row in edit mode so the value can be corrected instead of silently becoming a different schedule. The narrow-window case has its own message, because that one is genuinely non-obvious: six meals between 08:00 and 11:00 would round two checkpoints onto the same hour, and a duplicate slot silently drops one. Three structural notes: - The row's widgets are their own module. _gatelock_calendar_widgets.py was at 216 of the 250-line cap and is now 233; adding the row inline would have breached it. - The five new StringVars are grouped in a ScheduleVars dataclass rather than flattened onto CalendarVars, which pylint flagged at 11 attributes. Grouping two unrelated concerns separately is the better shape anyway. - _gate_fixtures.py's _GATE_TK_MODULES is a hand-listed patch target, so both new modules are registered there. Left out, they build real tk widgets under a fake parent -- which is how it failed the first time, loudly, rather than silently opening a window. 785 tests, 100% branch coverage. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159b4E5hh512jYUeaJVNxfM
110 lines
3.2 KiB
Python
110 lines
3.2 KiB
Python
"""The History tab's meal-schedule row widgets.
|
|
|
|
Split out of :mod:`._gatelock_calendar_widgets` (216 lines against the repo's
|
|
250-line cap) rather than added to it. The behaviour behind these widgets
|
|
lives in :mod:`._gatelock_scheduleedit`, mirroring how the budget row's
|
|
widgets and its edit/save logic are split.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import tkinter as tk
|
|
from typing import TYPE_CHECKING
|
|
|
|
from gatelock import make_button
|
|
|
|
from diet_guard._gatelock_calendar_types import _COLORS, _FIELD_BG
|
|
from diet_guard._gatelock_spacing import SM, XS
|
|
from diet_guard._gatelock_typography import BODY, LABEL
|
|
from diet_guard._gatelock_ui import BG, FG
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Callable
|
|
|
|
from diet_guard._gatelock_calendar_types import CalendarVars
|
|
|
|
__all__ = ["build_schedule_row"]
|
|
|
|
_ENTRY_WIDTH = 4
|
|
|
|
|
|
def _spin_entry(row: tk.Frame, variable: tk.StringVar) -> tk.Entry:
|
|
"""Return one read-only-by-default numeric entry in the schedule row."""
|
|
entry = tk.Entry(
|
|
row,
|
|
textvariable=variable,
|
|
font=(_COLORS.font_family, BODY),
|
|
width=_ENTRY_WIDTH,
|
|
bg=_FIELD_BG,
|
|
fg=FG,
|
|
insertbackground=FG,
|
|
justify="center",
|
|
state="readonly",
|
|
readonlybackground=_FIELD_BG,
|
|
)
|
|
entry.pack(side="left", padx=(XS, SM), ipady=XS)
|
|
return entry
|
|
|
|
|
|
def build_schedule_row(
|
|
parent: tk.Frame,
|
|
vars_: CalendarVars,
|
|
on_edit_or_save_schedule: Callable[[], None],
|
|
) -> tuple[tk.Entry, tk.Entry, tk.Entry, tk.Button, tk.Label]:
|
|
"""Build the meal-schedule row.
|
|
|
|
Returns the three hour/count entries, the edit button, and the status
|
|
label, in that order. Like the budget row the entries start read-only:
|
|
the schedule is displayed but not directly typeable until "Edit".
|
|
|
|
Returns:
|
|
``(first_entry, last_entry, count_entry, edit_button, status_label)``.
|
|
"""
|
|
row = tk.Frame(parent, bg=BG)
|
|
row.pack(pady=(SM, XS))
|
|
tk.Label(
|
|
row,
|
|
text="Meals:",
|
|
font=(_COLORS.font_family, LABEL),
|
|
bg=BG,
|
|
fg=FG,
|
|
).pack(side="left")
|
|
first_entry = _spin_entry(row, vars_.schedule.first)
|
|
tk.Label(
|
|
row,
|
|
text="to",
|
|
font=(_COLORS.font_family, LABEL),
|
|
bg=BG,
|
|
fg=FG,
|
|
).pack(side="left")
|
|
last_entry = _spin_entry(row, vars_.schedule.last)
|
|
tk.Label(
|
|
row,
|
|
text="x",
|
|
font=(_COLORS.font_family, LABEL),
|
|
bg=BG,
|
|
fg=FG,
|
|
).pack(side="left")
|
|
count_entry = _spin_entry(row, vars_.schedule.count)
|
|
edit_button = make_button(row, _COLORS, "Edit", on_edit_or_save_schedule)
|
|
edit_button.pack(side="left")
|
|
|
|
# The derived checkpoint times, so the effect of a change is visible
|
|
# before it is saved rather than only after the next lock.
|
|
tk.Label(
|
|
parent,
|
|
textvariable=vars_.schedule.times,
|
|
font=(_COLORS.font_family, LABEL),
|
|
bg=BG,
|
|
fg=FG,
|
|
).pack(pady=(0, XS))
|
|
status_label = tk.Label(
|
|
parent,
|
|
textvariable=vars_.schedule.status,
|
|
font=(_COLORS.font_family, LABEL),
|
|
bg=BG,
|
|
fg=FG,
|
|
)
|
|
status_label.pack(pady=(0, XS))
|
|
return first_entry, last_entry, count_entry, edit_button, status_label
|