Initial commit
This commit is contained in:
@@ -0,0 +1,557 @@
|
||||
"""Compact Mode window — floating parts lookup for packing station use.
|
||||
|
||||
Standalone CTkToplevel with clipboard watcher, configurable layout (vertical /
|
||||
horizontal), label printing, and optional always-on-top pinning.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
import customtkinter as ctk
|
||||
|
||||
from .data import PartsRepo
|
||||
from . import theme as T
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
_SKU_RE = re.compile(r'\b(\d{6,}-\d{3,}[LR]?)\b')
|
||||
_ADDR_RE = re.compile(r'[A-Za-z][A-Za-z\s]+,\s*[A-Z]{2}\s+\d{5}')
|
||||
|
||||
_WIN_W_VERT = 420
|
||||
_WIN_H_VERT = 640
|
||||
_WIN_W_HORIZ = 800
|
||||
_WIN_H_HORIZ = 440
|
||||
|
||||
|
||||
class CompactModeWindow(ctk.CTkToplevel):
|
||||
"""Compact floating parts-lookup window."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
parent,
|
||||
repo: PartsRepo | None,
|
||||
labels,
|
||||
state: dict,
|
||||
save_state,
|
||||
) -> None:
|
||||
super().__init__(parent)
|
||||
self._repo = repo
|
||||
self._labels = labels
|
||||
self._state = state
|
||||
self._save_state = save_state
|
||||
|
||||
self._auto_paste = bool(state.get("compact_auto_paste", True))
|
||||
self._auto_clear = bool(state.get("compact_auto_clear", False))
|
||||
self._horizontal = bool(state.get("compact_horizontal", False))
|
||||
self._pinned = bool(state.get("compact_pinned", False))
|
||||
|
||||
self._results: list[dict] = []
|
||||
self._sel_part: dict | None = None
|
||||
self._clip_last: str = ""
|
||||
self._clip_job: str | None = None
|
||||
self._hint_job: str | None = None
|
||||
|
||||
self.title("Compact Mode — Parts Lookup")
|
||||
self.minsize(340, 400)
|
||||
if self._pinned:
|
||||
self.attributes("-topmost", True)
|
||||
|
||||
self._apply_window_geometry()
|
||||
self._build()
|
||||
self._start_clipboard_watcher()
|
||||
self.lift()
|
||||
self.focus_set()
|
||||
|
||||
# ── Geometry ──────────────────────────────────────────────────────────────
|
||||
|
||||
def _apply_window_geometry(self) -> None:
|
||||
if self._horizontal:
|
||||
self.geometry(f"{_WIN_W_HORIZ}x{_WIN_H_HORIZ}")
|
||||
else:
|
||||
self.geometry(f"{_WIN_W_VERT}x{_WIN_H_VERT}")
|
||||
|
||||
# ── Build ─────────────────────────────────────────────────────────────────
|
||||
|
||||
def _build(self) -> None:
|
||||
self.columnconfigure(0, weight=1)
|
||||
self.rowconfigure(0, weight=1)
|
||||
if self._horizontal:
|
||||
self._build_horizontal()
|
||||
else:
|
||||
self._build_vertical()
|
||||
|
||||
def _teardown_layout(self) -> None:
|
||||
for child in self.winfo_children():
|
||||
child.destroy()
|
||||
|
||||
def _build_vertical(self) -> None:
|
||||
outer = ctk.CTkFrame(self, fg_color=T.WINDOW_BG, corner_radius=0)
|
||||
outer.grid(row=0, column=0, sticky="nsew")
|
||||
outer.columnconfigure(0, weight=1)
|
||||
outer.rowconfigure(3, weight=1) # results list expands
|
||||
|
||||
# Row 0: search entry
|
||||
self._search_var = ctk.StringVar()
|
||||
self._search_var.trace_add("write", self._on_search_change)
|
||||
self._search_entry = ctk.CTkEntry(
|
||||
outer, textvariable=self._search_var,
|
||||
placeholder_text="Search SKU or description...",
|
||||
font=T.FONT_LABEL, height=32,
|
||||
)
|
||||
self._search_entry.grid(row=0, column=0, sticky="ew", padx=8, pady=(8, 4))
|
||||
|
||||
# Row 1: Clear + Print Label buttons
|
||||
btn_row = ctk.CTkFrame(outer, fg_color="transparent")
|
||||
btn_row.grid(row=1, column=0, sticky="ew", padx=8, pady=(0, 4))
|
||||
btn_row.columnconfigure(0, weight=1)
|
||||
btn_row.columnconfigure(1, weight=1)
|
||||
|
||||
ctk.CTkButton(
|
||||
btn_row, text="Clear", command=self.clear_search, height=30,
|
||||
font=T.FONT_LABEL,
|
||||
fg_color=T.NEUTRAL_BTN_BG, hover_color=T.NEUTRAL_BTN_HOVER,
|
||||
text_color=T.TEXT_PRIMARY, border_width=1,
|
||||
border_color=T.NEUTRAL_BTN_BORDER, corner_radius=5,
|
||||
).grid(row=0, column=0, sticky="ew", padx=(0, 3))
|
||||
|
||||
ctk.CTkButton(
|
||||
btn_row, text="Print Label", command=self.print_label, height=30,
|
||||
font=T.FONT_LABEL,
|
||||
fg_color=T.ACCENT, hover_color=T.ACCENT_HOVER,
|
||||
text_color=T.ACCENT_TEXT, border_width=1,
|
||||
border_color=T.ACCENT_BORDER, corner_radius=5,
|
||||
).grid(row=0, column=1, sticky="ew", padx=(3, 0))
|
||||
|
||||
# Row 2: clipboard hint (hidden when empty)
|
||||
self._clip_hint = ctk.CTkLabel(
|
||||
outer, text="", font=T.FONT_STATUS, text_color=T.TEXT_MUTED, anchor="w",
|
||||
)
|
||||
self._clip_hint.grid(row=2, column=0, sticky="w", padx=10)
|
||||
|
||||
# Row 3: results list (expands)
|
||||
self._results_frame = ctk.CTkScrollableFrame(
|
||||
outer, fg_color=T.PANEL_BG, corner_radius=6,
|
||||
)
|
||||
self._results_frame.grid(row=3, column=0, sticky="nsew", padx=8, pady=(0, 4))
|
||||
self._results_frame.columnconfigure(0, weight=1)
|
||||
|
||||
# Row 4: Ship-to Address
|
||||
addr_card = ctk.CTkFrame(outer, fg_color=T.PANEL_BG, corner_radius=6)
|
||||
addr_card.grid(row=4, column=0, sticky="ew", padx=8, pady=(0, 4))
|
||||
addr_card.columnconfigure(0, weight=1)
|
||||
|
||||
ctk.CTkLabel(
|
||||
addr_card, text="Ship-to Address",
|
||||
font=T.FONT_LABEL, text_color=T.TEXT_MUTED, anchor="w",
|
||||
).grid(row=0, column=0, sticky="w", padx=10, pady=(8, 2))
|
||||
|
||||
self._address_box = ctk.CTkTextbox(
|
||||
addr_card, height=80, font=T.FONT_LABEL,
|
||||
fg_color=T.PANEL_BG, border_width=1, border_color=T.BORDER, corner_radius=4,
|
||||
)
|
||||
self._address_box.grid(row=1, column=0, sticky="ew", padx=8, pady=(0, 6))
|
||||
|
||||
ctk.CTkButton(
|
||||
addr_card, text="Print Address", command=self.print_address, height=32,
|
||||
font=T.FONT_LABEL,
|
||||
fg_color=T.ACCENT, hover_color=T.ACCENT_HOVER,
|
||||
text_color=T.ACCENT_TEXT, border_width=1,
|
||||
border_color=T.ACCENT_BORDER, corner_radius=5,
|
||||
).grid(row=2, column=0, sticky="ew", padx=8, pady=(0, 8))
|
||||
|
||||
# Row 5: bottom bar — Auto Paste toggle + gear
|
||||
bottom = ctk.CTkFrame(outer, fg_color=T.PANEL_BG, corner_radius=0, height=36)
|
||||
bottom.grid(row=5, column=0, sticky="ew")
|
||||
bottom.pack_propagate(False)
|
||||
bottom.columnconfigure(0, weight=1)
|
||||
|
||||
self._auto_paste_switch = ctk.CTkSwitch(
|
||||
bottom, text="Auto Paste",
|
||||
font=T.FONT_STATUS, text_color=T.TEXT_MUTED,
|
||||
command=self._on_auto_paste_toggle,
|
||||
)
|
||||
if self._auto_paste:
|
||||
self._auto_paste_switch.select()
|
||||
self._auto_paste_switch.pack(side="left", padx=(10, 0), pady=8)
|
||||
|
||||
ctk.CTkButton(
|
||||
bottom, text="⚙", width=28, height=24,
|
||||
font=T.FONT_LABEL,
|
||||
fg_color="transparent", hover_color=T.NEUTRAL_BTN_HOVER,
|
||||
text_color=T.TEXT_MUTED, border_width=0, corner_radius=4,
|
||||
command=self.show_label_settings,
|
||||
).pack(side="right", padx=(0, 8), pady=6)
|
||||
|
||||
def _build_horizontal(self) -> None:
|
||||
outer = ctk.CTkFrame(self, fg_color=T.WINDOW_BG, corner_radius=0)
|
||||
outer.grid(row=0, column=0, sticky="nsew")
|
||||
outer.columnconfigure(0, weight=1)
|
||||
outer.columnconfigure(1, weight=1)
|
||||
outer.rowconfigure(1, weight=1)
|
||||
|
||||
left = ctk.CTkFrame(outer, fg_color="transparent")
|
||||
left.grid(row=0, column=0, sticky="nsew", padx=(0, 2))
|
||||
left.columnconfigure(0, weight=1)
|
||||
left.rowconfigure(3, weight=1)
|
||||
|
||||
self._search_var = ctk.StringVar()
|
||||
self._search_var.trace_add("write", self._on_search_change)
|
||||
self._search_entry = ctk.CTkEntry(
|
||||
left, textvariable=self._search_var,
|
||||
placeholder_text="Search SKU or description...",
|
||||
font=T.FONT_LABEL, height=32,
|
||||
)
|
||||
self._search_entry.grid(row=0, column=0, sticky="ew", padx=8, pady=(8, 4))
|
||||
|
||||
btn_row = ctk.CTkFrame(left, fg_color="transparent")
|
||||
btn_row.grid(row=1, column=0, sticky="ew", padx=8, pady=(0, 4))
|
||||
btn_row.columnconfigure(0, weight=1)
|
||||
btn_row.columnconfigure(1, weight=1)
|
||||
ctk.CTkButton(
|
||||
btn_row, text="Clear", command=self.clear_search, height=30,
|
||||
font=T.FONT_LABEL,
|
||||
fg_color=T.NEUTRAL_BTN_BG, hover_color=T.NEUTRAL_BTN_HOVER,
|
||||
text_color=T.TEXT_PRIMARY, border_width=1,
|
||||
border_color=T.NEUTRAL_BTN_BORDER, corner_radius=5,
|
||||
).grid(row=0, column=0, sticky="ew", padx=(0, 3))
|
||||
ctk.CTkButton(
|
||||
btn_row, text="Print Label", command=self.print_label, height=30,
|
||||
font=T.FONT_LABEL,
|
||||
fg_color=T.ACCENT, hover_color=T.ACCENT_HOVER,
|
||||
text_color=T.ACCENT_TEXT, border_width=1,
|
||||
border_color=T.ACCENT_BORDER, corner_radius=5,
|
||||
).grid(row=0, column=1, sticky="ew", padx=(3, 0))
|
||||
|
||||
self._clip_hint = ctk.CTkLabel(
|
||||
left, text="", font=T.FONT_STATUS, text_color=T.TEXT_MUTED, anchor="w",
|
||||
)
|
||||
self._clip_hint.grid(row=2, column=0, sticky="w", padx=10)
|
||||
|
||||
self._results_frame = ctk.CTkScrollableFrame(
|
||||
left, fg_color=T.PANEL_BG, corner_radius=6,
|
||||
)
|
||||
self._results_frame.grid(row=3, column=0, sticky="nsew", padx=8, pady=(0, 8))
|
||||
self._results_frame.columnconfigure(0, weight=1)
|
||||
|
||||
right = ctk.CTkFrame(outer, fg_color="transparent")
|
||||
right.grid(row=0, column=1, sticky="nsew")
|
||||
right.columnconfigure(0, weight=1)
|
||||
right.rowconfigure(1, weight=1)
|
||||
|
||||
ctk.CTkLabel(
|
||||
right, text="Ship-to Address",
|
||||
font=T.FONT_LABEL, text_color=T.TEXT_MUTED, anchor="w",
|
||||
).grid(row=0, column=0, sticky="w", padx=10, pady=(8, 2))
|
||||
|
||||
self._address_box = ctk.CTkTextbox(
|
||||
right, font=T.FONT_LABEL, fg_color=T.PANEL_BG,
|
||||
border_width=1, border_color=T.BORDER, corner_radius=4,
|
||||
)
|
||||
self._address_box.grid(row=1, column=0, sticky="nsew", padx=8, pady=(0, 6))
|
||||
|
||||
ctk.CTkButton(
|
||||
right, text="Print Address", command=self.print_address, height=32,
|
||||
font=T.FONT_LABEL,
|
||||
fg_color=T.ACCENT, hover_color=T.ACCENT_HOVER,
|
||||
text_color=T.ACCENT_TEXT, border_width=1,
|
||||
border_color=T.ACCENT_BORDER, corner_radius=5,
|
||||
).grid(row=2, column=0, sticky="ew", padx=8, pady=(0, 8))
|
||||
|
||||
# ── Toggle actions ────────────────────────────────────────────────────────
|
||||
|
||||
def _on_auto_paste_toggle(self) -> None:
|
||||
self._auto_paste = not self._auto_paste
|
||||
self._state["compact_auto_paste"] = self._auto_paste
|
||||
self._save_state(self._state)
|
||||
|
||||
# ── Search ────────────────────────────────────────────────────────────────
|
||||
|
||||
def _on_search_change(self, *_) -> None:
|
||||
self.after(80, self._run_search)
|
||||
|
||||
def _run_search(self) -> None:
|
||||
query = self._search_var.get().strip()
|
||||
if not query or self._repo is None:
|
||||
self._render_results([])
|
||||
return
|
||||
results = self._repo.search(query, limit=10)
|
||||
self._render_results(results)
|
||||
|
||||
def _render_results(self, results: list[dict]) -> None:
|
||||
self._results = results
|
||||
self._cm_rows: list[dict] = []
|
||||
for child in self._results_frame.winfo_children():
|
||||
child.destroy()
|
||||
self._results_frame.columnconfigure(0, weight=1)
|
||||
for i, part in enumerate(results):
|
||||
card = ctk.CTkFrame(
|
||||
self._results_frame, fg_color=T.CARD_BG,
|
||||
border_color=T.BORDER, border_width=1,
|
||||
corner_radius=6, cursor="hand2",
|
||||
)
|
||||
card.grid(row=i, column=0, sticky="ew", padx=8, pady=(0, 4))
|
||||
card.columnconfigure(0, weight=1)
|
||||
|
||||
sku = part.get("stock_number", "") or part.get("STOCK NUMBER", "")
|
||||
desc = part.get("description", "") or part.get("DESCRIPTION", "")
|
||||
|
||||
sku_lbl = ctk.CTkLabel(
|
||||
card, text=sku, font=T.FONT_RESULT_TITLE,
|
||||
text_color=T.TEXT_PRIMARY, anchor="w",
|
||||
)
|
||||
sku_lbl.grid(row=0, column=0, sticky="w", padx=14, pady=(9, 1))
|
||||
|
||||
desc_lbl = ctk.CTkLabel(
|
||||
card, text=desc[:72] + ("…" if len(desc) > 72 else ""),
|
||||
font=T.FONT_RESULT, text_color=T.TEXT_MUTED,
|
||||
anchor="w", wraplength=260, justify="left",
|
||||
)
|
||||
desc_lbl.grid(row=1, column=0, sticky="w", padx=14, pady=(0, 9))
|
||||
|
||||
row_widgets = {"card": card, "sku_lbl": sku_lbl, "desc_lbl": desc_lbl}
|
||||
self._cm_rows.append(row_widgets)
|
||||
|
||||
def _make_handlers(idx, p):
|
||||
def _click(_e=None):
|
||||
self._on_result_click(p, idx)
|
||||
def _enter(_e=None):
|
||||
if self._cm_sel_idx != idx:
|
||||
card.configure(fg_color=T.CARD_BG_HOVER)
|
||||
def _leave(_e=None):
|
||||
if self._cm_sel_idx != idx:
|
||||
card.configure(fg_color=T.CARD_BG)
|
||||
return _click, _enter, _leave
|
||||
|
||||
click_fn, enter_fn, leave_fn = _make_handlers(i, part)
|
||||
for w in (card, sku_lbl, desc_lbl):
|
||||
w.bind("<Button-1>", click_fn)
|
||||
w.bind("<Enter>", enter_fn)
|
||||
w.bind("<Leave>", leave_fn)
|
||||
|
||||
self._cm_sel_idx: int | None = None
|
||||
if results:
|
||||
self._on_result_click(results[0], 0)
|
||||
|
||||
def _cm_highlight(self, index: int) -> None:
|
||||
for i, row_w in enumerate(self._cm_rows):
|
||||
selected = (i == index)
|
||||
card = row_w["card"]
|
||||
sku_lbl = row_w["sku_lbl"]
|
||||
desc_lbl = row_w["desc_lbl"]
|
||||
if selected:
|
||||
card.configure(fg_color=T.ACCENT, border_color=T.ACCENT)
|
||||
sku_lbl.configure(text_color=T.ACCENT_TEXT)
|
||||
desc_lbl.configure(text_color=T.ACCENT_TEXT)
|
||||
else:
|
||||
card.configure(fg_color=T.CARD_BG, border_color=T.BORDER)
|
||||
sku_lbl.configure(text_color=T.TEXT_PRIMARY)
|
||||
desc_lbl.configure(text_color=T.TEXT_MUTED)
|
||||
|
||||
def _on_result_click(self, part: dict, index: int | None = None) -> None:
|
||||
self._sel_part = part
|
||||
if index is not None:
|
||||
self._cm_sel_idx = index
|
||||
self._cm_highlight(index)
|
||||
addr = self.assembled_address()
|
||||
if addr:
|
||||
try:
|
||||
self._address_box.delete("1.0", "end")
|
||||
self._address_box.insert("1.0", addr)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def assembled_address(self) -> str:
|
||||
if not self._sel_part:
|
||||
return ""
|
||||
lines = []
|
||||
for key in ("address1", "address2", "city", "state", "zip"):
|
||||
val = self._sel_part.get(key, "")
|
||||
if val:
|
||||
lines.append(str(val))
|
||||
return "\n".join(lines)
|
||||
|
||||
# ── Clipboard watcher ─────────────────────────────────────────────────────
|
||||
|
||||
def _start_clipboard_watcher(self) -> None:
|
||||
try:
|
||||
self._clip_last = self.clipboard_get()
|
||||
except Exception:
|
||||
self._clip_last = ""
|
||||
self._schedule_clipboard_poll()
|
||||
|
||||
def _schedule_clipboard_poll(self) -> None:
|
||||
try:
|
||||
self._clip_job = self.after(500, self._poll_clipboard)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _poll_clipboard(self) -> None:
|
||||
try:
|
||||
text = self.clipboard_get()
|
||||
except Exception:
|
||||
text = self._clip_last
|
||||
if text != self._clip_last:
|
||||
self._clip_last = text
|
||||
self._handle_clipboard(text)
|
||||
self._schedule_clipboard_poll()
|
||||
|
||||
def _handle_clipboard(self, text: str) -> None:
|
||||
if not self._auto_paste:
|
||||
return
|
||||
sku_m = _SKU_RE.search(text)
|
||||
if sku_m:
|
||||
sku = sku_m.group(1)
|
||||
self._show_clipboard_hint(f"Pasted SKU: {sku}")
|
||||
if self._auto_clear:
|
||||
self._search_var.set("")
|
||||
self._search_var.set(sku)
|
||||
try:
|
||||
self._search_entry.icursor("end")
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
addr_m = _ADDR_RE.search(text)
|
||||
if addr_m:
|
||||
self._show_clipboard_hint("Address detected")
|
||||
try:
|
||||
self._address_box.delete("1.0", "end")
|
||||
self._address_box.insert("1.0", text.strip())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _show_clipboard_hint(self, msg: str) -> None:
|
||||
try:
|
||||
self._clip_hint.configure(text=msg)
|
||||
if self._hint_job:
|
||||
self.after_cancel(self._hint_job)
|
||||
self._hint_job = self.after(3000, self._hide_clipboard_hint)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _hide_clipboard_hint(self) -> None:
|
||||
try:
|
||||
self._clip_hint.configure(text="")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ── Print actions ─────────────────────────────────────────────────────────
|
||||
|
||||
def print_label(self) -> None:
|
||||
if not self._labels or not self._sel_part:
|
||||
return
|
||||
sku = self._sel_part.get("sku", "")
|
||||
desc = self._sel_part.get("description", "")
|
||||
try:
|
||||
self._labels.print_label(sku, desc)
|
||||
except Exception as exc:
|
||||
log.warning("Compact mode print_label failed: %s", exc)
|
||||
|
||||
def print_address(self) -> None:
|
||||
if not self._labels:
|
||||
return
|
||||
try:
|
||||
address = self._address_box.get("1.0", "end").strip()
|
||||
except Exception:
|
||||
address = ""
|
||||
if not address:
|
||||
return
|
||||
try:
|
||||
self._labels.print_address_label(address)
|
||||
except Exception as exc:
|
||||
log.warning("Compact mode print_address failed: %s", exc)
|
||||
|
||||
def show_label_settings(self) -> None:
|
||||
if not self._labels:
|
||||
return
|
||||
popup = ctk.CTkToplevel(self)
|
||||
popup.title("Label Printer Settings")
|
||||
popup.geometry("360x200")
|
||||
popup.grab_set()
|
||||
popup.columnconfigure(0, weight=1)
|
||||
|
||||
ctk.CTkLabel(
|
||||
popup, text="Select Label Printer",
|
||||
font=T.FONT_LABEL, text_color=T.TEXT_PRIMARY,
|
||||
).grid(row=0, column=0, pady=(16, 4))
|
||||
|
||||
printers: list[str] = []
|
||||
try:
|
||||
printers = self._labels.available_printers()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
current = ""
|
||||
try:
|
||||
current = self._labels.get_printer() or ""
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
var = ctk.StringVar(value=current)
|
||||
if printers:
|
||||
ctk.CTkOptionMenu(
|
||||
popup, values=printers, variable=var,
|
||||
font=T.FONT_LABEL, width=280,
|
||||
).grid(row=1, column=0, pady=6)
|
||||
else:
|
||||
ctk.CTkLabel(
|
||||
popup, text="No printers found",
|
||||
font=T.FONT_LABEL, text_color=T.TEXT_MUTED,
|
||||
).grid(row=1, column=0, pady=6)
|
||||
|
||||
def _apply() -> None:
|
||||
self._set_printer(var.get())
|
||||
popup.destroy()
|
||||
|
||||
btn_row = ctk.CTkFrame(popup, fg_color="transparent")
|
||||
btn_row.grid(row=2, column=0, pady=8)
|
||||
ctk.CTkButton(btn_row, text="Apply", width=80, command=_apply).pack(
|
||||
side="left", padx=4)
|
||||
ctk.CTkButton(
|
||||
btn_row, text="Preferences...", width=108,
|
||||
command=self._open_printer_preferences,
|
||||
).pack(side="left", padx=4)
|
||||
|
||||
def _set_printer(self, name: str) -> None:
|
||||
if self._labels and name:
|
||||
try:
|
||||
self._labels.set_printer(name)
|
||||
except Exception as exc:
|
||||
log.warning("Could not set printer: %s", exc)
|
||||
|
||||
def _open_printer_preferences(self) -> None:
|
||||
if self._labels:
|
||||
try:
|
||||
self._labels.open_preferences()
|
||||
except Exception as exc:
|
||||
log.warning("Could not open printer preferences: %s", exc)
|
||||
|
||||
# ── Search layout sync (called after resize) ──────────────────────────────
|
||||
|
||||
def _on_window_resize(self, event) -> None:
|
||||
pass
|
||||
|
||||
def _sync_search_layout(self) -> None:
|
||||
pass
|
||||
|
||||
def _apply_search_layout(self) -> None:
|
||||
pass
|
||||
|
||||
# ── Public ────────────────────────────────────────────────────────────────
|
||||
|
||||
def clear_search(self) -> None:
|
||||
try:
|
||||
self._search_var.set("")
|
||||
self._search_entry.focus_set()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def destroy(self) -> None:
|
||||
for job in (self._clip_job, self._hint_job):
|
||||
if job:
|
||||
try:
|
||||
self.after_cancel(job)
|
||||
except Exception:
|
||||
pass
|
||||
super().destroy()
|
||||
Reference in New Issue
Block a user