Files
2026-07-17 15:29:53 -04:00

137 lines
5.3 KiB
Python

"""Modal popup invoked when a device sends a Request BLE Variable(s) op.
The dialog lists the variable names the device asked for, pre-fills with the
current device-profile values (if any), and returns the user's edits via the
on_submit callback. Clicking Cancel returns None — the BLE client falls back
to the existing values in that case.
"""
import tkinter as tk
from tkinter import messagebox
class RequestVariableDialog(tk.Toplevel):
_BG = "#2D2D3D"
_ROW_BG = "#252535"
_ENTRY_BG = "#1E1E2E"
_FG = "#FFFFFF"
_DIM_FG = "#AAAAAA"
_BTN_BG = "#3D3D5C"
_SAVE_BG = "#0077CC"
def __init__(self, parent, mac: str, names: list, current: dict,
on_submit, play_sound: bool = True):
super().__init__(parent)
self.title(f"Variable Request — {mac}")
self.geometry("520x420")
self.minsize(380, 240)
self.configure(bg=self._BG)
self.transient(parent)
self.grab_set()
self._on_submit = on_submit
self._mac = mac
self._names = list(names)
self._current = dict(current or {})
self._rows = []
try:
import ctypes
self.update_idletasks()
hwnd = ctypes.windll.user32.GetParent(self.winfo_id())
ctypes.windll.dwmapi.DwmSetWindowAttribute(
hwnd, 20, ctypes.byref(ctypes.c_int(1)), ctypes.sizeof(ctypes.c_int))
except Exception:
pass
self._build_ui()
# Intentionally NOT binding <Return> globally on the Toplevel: a stray
# Enter keystroke pending in the OS keyboard queue when this dialog
# focus_force()s itself in would auto-submit before the user types,
# send empty values back to the device, and "end the routine."
# Submission goes through the Send to Device button only.
self.protocol("WM_DELETE_WINDOW", self._cancel)
if play_sound:
try:
import winsound
winsound.MessageBeep(winsound.MB_ICONEXCLAMATION)
except Exception:
pass
self.lift()
self.focus_force()
def _build_ui(self):
hdr = tk.Frame(self, bg=self._BG)
hdr.pack(fill="x", padx=14, pady=(12, 4))
tk.Label(hdr, text="Variable Request", bg=self._BG, fg="#0AACFF",
font=("Segoe UI", 11, "bold")).pack(side="left")
tk.Label(hdr, text=f"Device {self._mac}", bg=self._BG, fg=self._DIM_FG,
font=("Segoe UI", 8)).pack(side="left", padx=(10, 0))
tk.Label(self, bg=self._BG, fg=self._DIM_FG, font=("Segoe UI", 8),
text="Fill in values below; the device will receive them and "
"the device profile will be updated.").pack(
anchor="w", padx=14)
outer = tk.Frame(self, bg=self._BG)
outer.pack(fill="both", expand=True, padx=14, pady=8)
canvas = tk.Canvas(outer, bg=self._BG, highlightthickness=0)
scrollbar = tk.Scrollbar(outer, orient="vertical", command=canvas.yview)
canvas.configure(yscrollcommand=scrollbar.set)
scrollbar.pack(side="right", fill="y")
canvas.pack(side="left", fill="both", expand=True)
rows_frame = tk.Frame(canvas, bg=self._BG)
rows_window = canvas.create_window((0, 0), window=rows_frame, anchor="nw")
rows_frame.bind("<Configure>",
lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
canvas.bind("<Configure>",
lambda e: canvas.itemconfig(rows_window, width=e.width))
first_entry = None
for name in self._names:
row = tk.Frame(rows_frame, bg=self._ROW_BG, pady=3)
row.pack(fill="x", pady=2)
tk.Label(row, text=name, bg=self._ROW_BG, fg=self._FG,
font=("Segoe UI", 9, "bold"), width=18, anchor="w").pack(
side="left", padx=(8, 4))
var = tk.StringVar(value=str(self._current.get(name, "")))
entry = tk.Entry(row, textvariable=var, bg=self._ENTRY_BG,
fg=self._FG, insertbackground=self._FG,
font=("Segoe UI", 9), relief="flat")
entry.pack(side="left", fill="x", expand=True, padx=(0, 8), ipady=3)
self._rows.append((name, var))
if first_entry is None:
first_entry = entry
if first_entry is not None:
first_entry.focus_set()
btns = tk.Frame(self, bg=self._BG)
btns.pack(fill="x", padx=14, pady=(4, 12))
tk.Button(btns, text="Cancel", bg=self._BTN_BG, fg=self._FG,
relief="flat", padx=12, font=("Segoe UI", 9),
command=self._cancel).pack(side="right", padx=(6, 0))
tk.Button(btns, text="Send to Device", bg=self._SAVE_BG, fg=self._FG,
relief="flat", padx=14, font=("Segoe UI", 9, "bold"),
command=self._submit).pack(side="right")
def _submit(self):
result = {name: var.get() for name, var in self._rows}
try:
self._on_submit(result)
except Exception as e:
messagebox.showerror("Variable Request", str(e), parent=self)
return
self.destroy()
def _cancel(self):
try:
self._on_submit(None)
except Exception:
pass
self.destroy()