Files
label_printer/test_gui_layout.py
T
ske087 93afa3c417 Add P-touch Labels for Asset: 63x20 mm label format for PT-E560BT
New three-column label format for Brother P-touch EDGE PT-E560BT asset tags:
logo | three static lines + variable field | QR code.

Layout and rendering
- ptouch_label.py: geometry engine with PDF and preview renderers. Both
  consume the same geometry, so the preview matches the print exactly.
- Code symbols are snapped to whole printer dots per module. Fractional
  modules are the usual reason a small QR code will not scan.
- QR is maximised to the printable height by default (16.4 mm, 4 dots per
  module) instead of a fixed 16 mm box (12.3 mm, 3 dots per module).
- Code payload is composed as prefix + inventory + suffix. A legacy
  {inventory} template is migrated on first save.

Hardware handling
- print_area.py reads the driver's real printable area via GetDeviceCaps.
  The PT-E560BT head is 128 dots at 180 dpi, so the maximum print height is
  18.06 mm even on 24 mm tape; the driver reports media as 65x24 mm but only
  about 61x18 mm is reachable. Content is fitted to the reachable area,
  otherwise the driver silently crops it.
- set_printer_resolution.py reads/switches the driver's resolution mode. The
  driver offers 180x180 (symmetric) and 180x360 (anisotropic, 2x across the
  tape); the anisotropic mode scaled output incorrectly.

GUI
- label_printer_ptouch_gui.py: 600x300 two-column panel - controls on the
  left, live preview on the right. Advanced settings for logo, static lines,
  code prefix/suffix and error correction.
- native_file_dialog.py: the standard Windows file picker, run in a
  subprocess because Tk and Kivy both want the main event loop.

Printing
- Print failures are reported honestly. print_to_printer previously returned
  True unconditionally, so a failed print claimed success and was logged as
  SUCCESS.
- SumatraPDF exit codes are now checked (4 = no printer, 5 = driver error).
- The PDF is sent with noscale so driver-side scaling cannot distort the QR
  module pitch.

Tests
- validate_ptouch_layout.py decodes the QR back out of the rendered preview
  module by module, proving the preview and PDF are the same layout.
- test_ptouch_pipeline.py writes to a temp config so it cannot overwrite
  operator settings.
- test_gui_layout.py exercises the real app class headlessly.
- test_print_reporting.py covers the failure paths.

Diagnostics: check_print_area.py, check_print_geometry.py, check_devmode.py,
print_calibration.py, print_measure_scale.py, capture_print_job.py.
2026-09-25 17:30:11 +03:00

90 lines
3.3 KiB
Python

"""
Headless test of the two-column GUI.
Instantiates the real app class and drives its handlers without opening a window,
so layout wiring and settings logic are verified automatically rather than by
looking at the screen.
"""
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
os.chdir(os.path.dirname(os.path.abspath(__file__)))
# Keep Kivy from parsing our argv; the real window backend is used but the app
# event loop is never started, so nothing is displayed.
os.environ.setdefault("KIVY_NO_ARGS", "1")
def main() -> int:
import label_printer_ptouch_gui as gui
print("1. Window is a 600 x 300 landscape panel")
print(f" WINDOW_SIZE : {gui.WINDOW_SIZE}")
landscape = gui.WINDOW_SIZE[0] > gui.WINDOW_SIZE[1]
print(f" landscape : {landscape} {'PASS' if landscape else 'FAIL'}")
print("\n2. App.config is not shadowed (the bug that crashed the settings panel)")
app = gui.PtouchPrinterApp()
has_collision = "config" in vars(app) and not hasattr(vars(app)["config"], "get")
print(f" app.config : {type(app.config).__name__}")
print(f" label_cfg : {type(app.label_cfg).__name__}")
cfg_ok = type(app.label_cfg).__name__ == "LabelConfig"
print(f" config valid: {cfg_ok} {'PASS' if cfg_ok else 'FAIL'}")
print("\n3. Two-column root layout")
root = app.build()
print(f" orientation : {root.orientation}")
print(f" columns : {len(root.children)}")
cols_ok = root.orientation == "horizontal" and len(root.children) == 2
print(f" {'PASS' if cols_ok else 'FAIL'}")
# Children are in reverse order of addition: preview column first, then controls.
preview_col, controls = root.children
print(f" controls col: {controls.orientation}, {len(controls.children)} items")
print(f" preview col : {preview_col.orientation}, {len(preview_col.children)} items")
print("\n4. Required widgets exist")
for name in (
"inventory_input",
"printer_spinner",
"print_button",
"preview",
"status_label",
):
present = hasattr(app, name)
print(f" {name:<18}: {'yes' if present else 'NO'}")
if not present:
return 1
print("\n5. Settings handlers are reachable (would have raised before)")
# Accessing these properties is what failed previously.
_ = app.label_cfg.logo_path
_ = app.label_cfg.static_lines
_ = app.label_cfg.qr_prefix
_ = app.label_cfg.qr_suffix
_ = app.label_cfg.qr_error_correction
print(" loaded without error: PASS")
print("\n6. Preview render works headless")
app.update_preview()
print(f" status text : {app.status_label.text!r}")
print(f" texture set : {app.preview._texture is not None}")
print("\n7. QR payload is built from the inventory field")
app.inventory_input.text = "INV-42"
app.update_preview()
print(f" status text : {app.status_label.text!r}")
payload_ok = "INV-42" in app.status_label.text
print(f" reflects field: {payload_ok} {'PASS' if payload_ok else 'FAIL'}")
print("\n" + "=" * 62)
passed = landscape and cfg_ok and cols_ok and payload_ok
print("RESULT:", "PASS" if passed else "FAIL")
return 0 if passed else 1
if __name__ == "__main__":
raise SystemExit(main())