screen-locker/screen_locker/_manual_workout_widgets.py
Krzysztof kuhy Rudnicki 60d8c3361b
Some checks are pending
pre-commit / pre-commit (push) Waiting to run
Tests / test (3.10) (push) Waiting to run
Tests / test (3.11) (push) Waiting to run
Tests / test (3.12) (push) Waiting to run
Redesign manual-workout form: two-column grid, drop phone-only fields
The manual-workout evidence form (shown on the lock screen and the status
window via the shared ManualWorkoutDialogMixin) was a cramped, narrow
centered column of tiny inputs with large empty margins, and included two
fields that make no sense to fill from a locked PC.

- Remove the vestigial "Google Maps link" (location_maps_link) and
  "Screenshot path" (proof_screenshot_path) fields from the form, the
  ManualWorkoutDraft dataclass, and build_entry(). Both were written to
  workout_log.json but never read by any code. The now-empty "Evidence"
  section is gone too. reservation_phone is kept.
- Lay the form out as a two-column grid that fills ~90% of the screen
  (the fullscreen container was shrink-wrapping to its widest child) with
  larger labels/inputs; reflection boxes span full width. Activity-specific
  fields now sit directly under their section header.
- Extract the low-level field/grid builders into a new
  ManualWorkoutFormWidgetsMixin (_manual_workout_widgets.py) so the dialog
  module stays under the 400-line cap; the dialog mixin inherits it, so the
  host classes need no change.

Tests updated for the removed fields and the pack->grid sport swap; added a
unit test for the grid cursor's odd-row bump. Full suite green at 100%.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PM3auNTifaVgcXWDizZrjH
2026-07-13 10:09:18 +02:00

177 lines
5.8 KiB
Python

"""Low-level widget/grid builders for the manual-workout evidence form.
Split out of ``_manual_workout_dialog`` so the dialog module stays under the
repo's file-length cap. These helpers build individual form cells and place
them into a two-column grid; the orchestration (which fields, in what order,
and submission) lives in ``ManualWorkoutDialogMixin``, which inherits this.
The host is expected to have initialised ``self._mw_grid_counters`` (per-master
grid cursors) and, for ``_mw_rpe_row``, ``self._mw_rpe_var``.
"""
from __future__ import annotations
import tkinter as tk
from screen_locker._constants import (
MANUAL_WORKOUT_RPE_MAX,
MANUAL_WORKOUT_RPE_MIN,
)
from screen_locker._ui_widgets import disable_paste
class ManualWorkoutFormWidgetsMixin:
"""Two-column grid placement + individual field-cell builders."""
def _mw_grid(
self, parent: tk.Widget, widget: tk.Widget, *, full: bool = False
) -> None:
"""Place ``widget`` into ``parent``'s two-column grid cursor.
A half-width item (``full=False``) fills the next cell (left, then
right); a ``full`` item starts a fresh row and spans both columns. Each
master keeps its own cursor (keyed by ``id(parent)``) so nested sport
frames grid independently of the main form.
"""
counters = self._mw_grid_counters
idx = counters.get(id(parent), 0)
if full and idx % 2 == 1:
idx += 1 # a full-width item can't share a row — bump to a new one
row, col = divmod(idx, 2)
widget.grid(
row=row,
column=col,
columnspan=2 if full else 1,
sticky="ew",
padx=12,
pady=6,
)
counters[id(parent)] = idx + (2 if full else 1)
def _mw_next_full_row(self, parent: tk.Widget) -> int:
"""Reserve a fresh full-width row for a manually-gridded widget.
Used for the twin sport frames, which share one slot and are gridded
directly (not through ``_mw_grid``); this advances the cursor past that
row so following fields resume cleanly.
"""
counters = self._mw_grid_counters
idx = counters.get(id(parent), 0)
if idx % 2 == 1:
idx += 1
counters[id(parent)] = idx + 2
return idx // 2
def _mw_section(self, parent: tk.Widget, title: str) -> None:
"""Add a full-width section heading inside the form."""
label = tk.Label(
parent,
text=title,
font=("Arial", 20, "bold"),
fg="#88ccff",
bg="#1a1a1a",
anchor="w",
)
self._mw_grid(parent, label, full=True)
def _mw_entry(self, parent: tk.Widget, label: str) -> tk.StringVar:
"""Add a half-width label+entry cell and return its backing StringVar.
Builds its own cell inline (rather than reusing ``_add_label_entry``,
which is pack-based and shared with the sick dialog) so it slots into
the two-column grid. Paste stays disabled via ``disable_paste``.
"""
var = tk.StringVar()
cell = tk.Frame(parent, bg="#1a1a1a")
tk.Label(
cell,
text=label,
font=("Arial", 16),
fg="white",
bg="#1a1a1a",
anchor="w",
).pack(fill="x")
entry = tk.Entry(
cell,
textvariable=var,
font=("Arial", 18),
bg="#2a2a2a",
fg="white",
insertbackground="white",
)
entry.pack(fill="x", pady=2)
disable_paste(entry)
self._mw_grid(parent, cell)
return var
def _mw_int_field(
self, parent: tk.Widget, label: str, *, frm: int = 0, to: int = 99
) -> tk.IntVar:
"""Add a half-width label + numeric Spinbox and return its IntVar."""
var = tk.IntVar(value=0)
row = tk.Frame(parent, bg="#1a1a1a")
tk.Label(
row,
text=label,
font=("Arial", 16),
fg="white",
bg="#1a1a1a",
).pack(side="left", padx=5)
tk.Spinbox(
row,
from_=frm,
to=to,
textvariable=var,
width=4,
font=("Arial", 16),
).pack(side="left", padx=5)
self._mw_grid(parent, row)
return var
def _mw_textbox(self, parent: tk.Widget, label: str) -> tk.Text:
"""Add a full-width label + multi-line Text cell and return the widget."""
cell = tk.Frame(parent, bg="#1a1a1a")
tk.Label(
cell,
text=label,
font=("Arial", 16),
fg="white",
bg="#1a1a1a",
anchor="w",
).pack(fill="x", pady=(5, 0))
text_widget = tk.Text(
cell,
height=3,
font=("Arial", 14),
bg="#2a2a2a",
fg="white",
insertbackground="white",
)
text_widget.pack(pady=2, fill="x")
disable_paste(text_widget)
self._mw_grid(parent, cell, full=True)
return text_widget
def _mw_rpe_row(self, parent: tk.Widget) -> None:
"""Add the RPE (rate of perceived exertion) spinbox row."""
row = tk.Frame(parent, bg="#1a1a1a")
tk.Label(
row,
text=(
f"RPE — perceived exertion "
f"({MANUAL_WORKOUT_RPE_MIN}-{MANUAL_WORKOUT_RPE_MAX}):"
),
font=("Arial", 16),
fg="white",
bg="#1a1a1a",
).pack(side="left", padx=5)
tk.Spinbox(
row,
from_=MANUAL_WORKOUT_RPE_MIN,
to=MANUAL_WORKOUT_RPE_MAX,
textvariable=self._mw_rpe_var,
width=4,
font=("Arial", 16),
).pack(side="left", padx=5)
self._mw_grid(parent, row)