Files
pqAutomationApp/app/views/panels/main_layout.py

1064 lines
39 KiB
Python
Raw Normal View History

"""主布局面板创建函数Step 6 重构)。"""
2026-04-20 11:48:38 +08:00
import re
2026-04-20 11:48:38 +08:00
import tkinter as tk
import ttkbootstrap as ttk
from drivers.UCD323_Enum import UCDEnum
from app.views.collapsing_frame import CollapsingFrame
from app.resources import load_icon
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from pqAutomationApp import PQAutomationApp
# ---------------------------------------------------------------------------
# 内部工具:现代化卡片容器
# ---------------------------------------------------------------------------
def _make_card(parent, icon: str, title: str) -> ttk.Frame:
"""构造一个"卡片"容器(圆角描边 + 顶部图标/标题)。
返回值是 outer card frame通过 ``outer._body`` 拿到内部 body Frame
旧代码里习惯把控件挂在 ``self.connection_frame`` "卡片本身"所以
保留 outer 作为对外别名但实际控件父级换成 body 以避开边框 padding
"""
outer = ttk.Frame(parent, style="Card.TFrame", padding=12)
header = ttk.Frame(outer, style="CardInner.TFrame")
header.pack(fill="x", pady=(0, 8))
ttk.Label(header, text=icon, style="CardIcon.TLabel").pack(side="left", padx=(0, 8))
ttk.Label(header, text=title, style="CardTitle.TLabel").pack(side="left")
body = ttk.Frame(outer, style="CardInner.TFrame")
body.pack(fill="both", expand=True)
outer._body = body # type: ignore[attr-defined]
return outer
def create_floating_config_panel(self: "PQAutomationApp"):
"""创建顶部"配置项"现代化折叠面板。
布局变化vs 旧版
- Unicode chevron + 整条 header 可点击折叠/展开
- header 上额外显示折叠状态预览``config_preview_var``
- header 右侧承载常驻操作工具条开始/停止/保存 不再放中部
- 内部三个区段从 LabelFrame 改为统一的 Card 样式
"""
2026-04-20 11:48:38 +08:00
cf = CollapsingFrame(self.control_frame_top)
cf.pack(fill="both")
self._config_collapsing = cf
# 配置项主体容器(卡片宿主)
2026-04-20 11:48:38 +08:00
self.config_panel_frame = ttk.Frame(cf)
# 折叠预览:呈现"测试类型 · 已选测试项"
self.config_preview_var = tk.StringVar(value="")
# header 右侧工具条占位 —— create_operation_frame 之后向这里挂按钮
self.toolbar_actions_frame: ttk.Frame | None = None
def _header_actions(parent: ttk.Frame):
# 暴露给 create_operation_frame 使用
self.toolbar_actions_frame = parent
cf.add(
self.config_panel_frame,
title="配置项",
preview_textvariable=self.config_preview_var,
header_actions=_header_actions,
)
# 卡片三栏
2026-04-20 11:48:38 +08:00
self.config_content_frame = ttk.Frame(self.config_panel_frame)
self.config_content_frame.pack(fill=tk.BOTH, expand=True, padx=8, pady=8)
2026-04-20 11:48:38 +08:00
config_row_frame = ttk.Frame(self.config_content_frame)
config_row_frame.pack(fill=tk.X, expand=False)
2026-04-20 11:48:38 +08:00
# 设备连接 卡片
2026-05-28 10:50:52 +08:00
connection_card = _make_card(config_row_frame, icon="01", title="设备连接")
connection_card.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 6))
self.connection_frame = connection_card._body # type: ignore[attr-defined]
2026-04-20 11:48:38 +08:00
# 测试项目 卡片
2026-05-28 10:50:52 +08:00
test_items_card = _make_card(config_row_frame, icon="02", title="测试项目")
test_items_card.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=6)
self.test_items_frame = test_items_card._body # type: ignore[attr-defined]
2026-04-20 11:48:38 +08:00
# 信号格式 卡片
2026-05-28 10:50:52 +08:00
signal_card = _make_card(config_row_frame, icon="03", title="信号格式")
signal_card.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(6, 0))
self.signal_format_frame = signal_card._body # type: ignore[attr-defined]
2026-04-20 11:48:38 +08:00
# 创建卡片内部内容(沿用旧函数,父级已是 body Frame
2026-04-20 11:48:38 +08:00
self.create_connection_content()
self.create_test_items_content()
self.create_signal_format_content()
# 默认收起 —— 与旧版行为保持一致
2026-04-20 11:48:38 +08:00
self.config_panel_frame.grid_remove()
self.config_panel_frame.btn.configure(image="closed")
# 初始化预览文本
refresh_config_preview(self)
def refresh_config_preview(self: "PQAutomationApp") -> None:
"""根据当前测试类型 + 已选测试项刷新折叠预览。"""
if not hasattr(self, "config_preview_var"):
return
type_labels = {
"screen_module": "屏模组",
"sdr_movie": "SDR Movie",
"hdr_movie": "HDR Movie",
}
current_type = getattr(self.config, "current_test_type", "")
type_label = type_labels.get(current_type, "")
item_labels = []
if current_type and hasattr(self, "test_items"):
info = self.test_items.get(current_type, {})
label_map = {code: name for name, code in info.get("items", [])}
if hasattr(self, "test_vars"):
for code, var in self.test_vars.items():
try:
if var.get():
item_labels.append(label_map.get(code, code))
except Exception:
pass
parts = []
if type_label:
parts.append(f"[{type_label}]")
if item_labels:
# 限制宽度,避免顶部条过挤
shown = item_labels[:5]
suffix = " \u2026" if len(item_labels) > 5 else ""
parts.append(" \u00b7 ".join(shown) + suffix)
self.config_preview_var.set(" ".join(parts))
2026-04-20 11:48:38 +08:00
def create_test_items_content(self: "PQAutomationApp"):
2026-04-20 11:48:38 +08:00
"""创建测试项目选项卡内容"""
# 创建测试项目字典,用于管理不同测试类型的选项
self.test_items = {
"screen_module": {
"frame": ttk.Frame(self.test_items_frame),
"items": [
("色域", "gamut"),
("Gamma", "gamma"),
("色度", "cct"),
("对比度", "contrast"),
],
},
"sdr_movie": {
"frame": ttk.Frame(self.test_items_frame),
"items": [
("色域", "gamut"),
("Gamma", "gamma"),
("色度", "cct"),
("对比度", "contrast"),
("色准", "accuracy"),
],
},
"hdr_movie": {
"frame": ttk.Frame(self.test_items_frame),
"items": [
("色域", "gamut"),
("EOTF", "eotf"),
("色度", "cct"),
("对比度", "contrast"),
("色准", "accuracy"),
],
},
}
# 根据当前测试类型创建复选框
self.test_vars = {}
self.update_test_items()
# 创建色度参数设置框架
self.create_cct_params_frame()
def create_signal_format_content(self: "PQAutomationApp"):
2026-04-20 11:48:38 +08:00
"""创建信号格式选项卡内容"""
self.signal_tabs = ttk.Notebook(self.signal_format_frame)
self.signal_tabs.pack(fill=tk.BOTH, expand=True)
# ==================== 屏模组格式设置 ====================
self.screen_module_signal_frame = ttk.Frame(self.signal_tabs)
self.screen_module_signal_frame.grid_columnconfigure(0, weight=0)
self.screen_module_signal_frame.grid_columnconfigure(1, weight=1)
2026-04-20 11:48:38 +08:00
self.signal_tabs.add(self.screen_module_signal_frame, text="屏模组测试")
screen_cfg = self.config.current_test_types.get("screen_module", {})
2026-04-20 11:48:38 +08:00
self.screen_module_timing_var = tk.StringVar(
value=screen_cfg.get("timing", "DMT 1920x 1080 @ 60Hz")
)
ttk.Label(self.screen_module_signal_frame, text="分辨率:").grid(
row=0, column=0, sticky=tk.W, padx=5, pady=2
2026-04-20 11:48:38 +08:00
)
screen_module_timing_combo = ttk.Combobox(
self.screen_module_signal_frame,
textvariable=self.screen_module_timing_var,
values=UCDEnum.TimingInfo.get_formatted_resolution_list(),
state="readonly",
)
screen_module_timing_combo.bind(
"<<ComboboxSelected>>", self.on_screen_module_timing_changed
)
screen_module_timing_combo.grid(row=0, column=1, sticky=tk.W, padx=5, pady=2)
ttk.Label(self.screen_module_signal_frame, text="色彩空间:").grid(
row=1, column=0, sticky=tk.W, padx=5, pady=2
)
self.screen_module_color_space_var = tk.StringVar(
value=screen_cfg.get("colorimetry", "sRGB")
)
screen_module_color_space_combo = ttk.Combobox(
self.screen_module_signal_frame,
textvariable=self.screen_module_color_space_var,
values=["sRGB", "BT.709", "BT.601", "BT.2020", "DCI-P3"],
width=10,
state="readonly",
)
screen_module_color_space_combo.bind(
"<<ComboboxSelected>>", self.on_screen_module_signal_format_changed
)
screen_module_color_space_combo.grid(row=1, column=1, sticky=tk.W, padx=5, pady=2)
ttk.Label(self.screen_module_signal_frame, text="数据范围:").grid(
row=2, column=0, sticky=tk.W, padx=5, pady=2
)
self.screen_module_data_range_var = tk.StringVar(
value=screen_cfg.get("data_range", UCDEnum.SignalFormat.DataRange.FULL)
)
screen_module_data_range_combo = ttk.Combobox(
self.screen_module_signal_frame,
textvariable=self.screen_module_data_range_var,
values=UCDEnum.SignalFormat.DataRange.get_list(),
width=10,
state="readonly",
)
screen_module_data_range_combo.bind(
"<<ComboboxSelected>>", self.on_screen_module_signal_format_changed
)
screen_module_data_range_combo.grid(row=2, column=1, sticky=tk.W, padx=5, pady=2)
default_screen_bpc = int(screen_cfg.get("bpc", 8))
default_screen_bit_depth = (
f"{default_screen_bpc}bit"
if f"{default_screen_bpc}bit" in UCDEnum.SignalFormat.BitDepth.get_list()
else UCDEnum.SignalFormat.BitDepth.BIT_8
)
ttk.Label(self.screen_module_signal_frame, text="编码位深:").grid(
row=3, column=0, sticky=tk.W, padx=5, pady=2
)
self.screen_module_bit_depth_var = tk.StringVar(value=default_screen_bit_depth)
screen_module_bit_depth_combo = ttk.Combobox(
self.screen_module_signal_frame,
textvariable=self.screen_module_bit_depth_var,
values=UCDEnum.SignalFormat.BitDepth.get_list(),
width=10,
state="readonly",
)
screen_module_bit_depth_combo.bind(
"<<ComboboxSelected>>", self.on_screen_module_signal_format_changed
)
screen_module_bit_depth_combo.grid(row=3, column=1, sticky=tk.W, padx=5, pady=2)
ttk.Label(self.screen_module_signal_frame, text="色彩格式:").grid(
row=4, column=0, sticky=tk.W, padx=5, pady=2
)
self.screen_module_output_format_var = tk.StringVar(
value=screen_cfg.get("color_format", UCDEnum.SignalFormat.OutputFormat.RGB)
)
screen_module_output_format_combo = ttk.Combobox(
self.screen_module_signal_frame,
textvariable=self.screen_module_output_format_var,
values=UCDEnum.SignalFormat.OutputFormat.get_list(),
width=10,
state="readonly",
)
screen_module_output_format_combo.bind(
"<<ComboboxSelected>>", self.on_screen_module_signal_format_changed
)
screen_module_output_format_combo.grid(row=4, column=1, sticky=tk.W, padx=5, pady=2)
2026-04-20 11:48:38 +08:00
# ==================== SDR信号格式设置 ====================
self.sdr_signal_frame = ttk.Frame(self.signal_tabs)
# 配置列权重
self.sdr_signal_frame.grid_columnconfigure(0, weight=0)
self.sdr_signal_frame.grid_columnconfigure(1, weight=1)
self.signal_tabs.add(self.sdr_signal_frame, text="SDR测试")
# 色彩空间
ttk.Label(self.sdr_signal_frame, text="色彩空间:").grid(
row=0, column=0, sticky=tk.W, padx=5, pady=2
)
self.sdr_color_space_var = tk.StringVar(value="BT.709")
sdr_color_space_combo = ttk.Combobox(
self.sdr_signal_frame,
textvariable=self.sdr_color_space_var,
2026-05-22 11:31:36 +08:00
values=["sRGB", "BT.709", "BT.601", "BT.2020", "DCI-P3"],
2026-04-20 11:48:38 +08:00
width=10,
state="readonly",
)
sdr_color_space_combo.grid(row=0, column=1, sticky=tk.W, padx=5, pady=2)
2026-05-22 11:31:36 +08:00
# Gamma测试参考值用于Gamma曲线绘制和色准计算
2026-04-20 11:48:38 +08:00
ttk.Label(self.sdr_signal_frame, text="Gamma:").grid(
row=1, column=0, sticky=tk.W, padx=5, pady=2
)
2026-05-22 11:31:36 +08:00
self.sdr_gamma_type_var = tk.StringVar(value=UCDEnum.SignalFormat.GammaType.GAMMA_22)
2026-04-20 11:48:38 +08:00
sdr_gamma_combo = ttk.Combobox(
self.sdr_signal_frame,
textvariable=self.sdr_gamma_type_var,
2026-05-22 11:31:36 +08:00
values=UCDEnum.SignalFormat.GammaType.get_list(),
2026-04-20 11:48:38 +08:00
width=10,
state="readonly",
)
sdr_gamma_combo.grid(row=1, column=1, sticky=tk.W, padx=5, pady=2)
# 数据范围
ttk.Label(self.sdr_signal_frame, text="数据范围:").grid(
row=2, column=0, sticky=tk.W, padx=5, pady=2
)
2026-05-22 11:31:36 +08:00
self.sdr_data_range_var = tk.StringVar(value=UCDEnum.SignalFormat.DataRange.FULL)
2026-04-20 11:48:38 +08:00
sdr_range_combo = ttk.Combobox(
self.sdr_signal_frame,
textvariable=self.sdr_data_range_var,
2026-05-22 11:31:36 +08:00
values=UCDEnum.SignalFormat.DataRange.get_list(),
2026-04-20 11:48:38 +08:00
width=10,
state="readonly",
)
sdr_range_combo.grid(row=2, column=1, sticky=tk.W, padx=5, pady=2)
# 编码位深
ttk.Label(self.sdr_signal_frame, text="编码位深:").grid(
row=3, column=0, sticky=tk.W, padx=5, pady=2
)
2026-05-22 11:31:36 +08:00
self.sdr_bit_depth_var = tk.StringVar(value=UCDEnum.SignalFormat.BitDepth.BIT_8)
2026-04-20 11:48:38 +08:00
sdr_bit_depth_combo = ttk.Combobox(
self.sdr_signal_frame,
textvariable=self.sdr_bit_depth_var,
2026-05-22 11:31:36 +08:00
values=UCDEnum.SignalFormat.BitDepth.get_list(),
2026-04-20 11:48:38 +08:00
width=10,
state="readonly",
)
sdr_bit_depth_combo.grid(row=3, column=1, sticky=tk.W, padx=5, pady=2)
2026-05-22 11:31:36 +08:00
# 分辨率
ttk.Label(self.sdr_signal_frame, text="分辨率:").grid(
row=4, column=0, sticky=tk.W, padx=5, pady=2
)
self.sdr_timing_var = tk.StringVar(
value=self.config.current_test_types.get("sdr_movie", {}).get(
"timing", "DMT 1920x1080@60Hz"
)
)
sdr_timing_combo = ttk.Combobox(
self.sdr_signal_frame,
textvariable=self.sdr_timing_var,
values=UCDEnum.TimingInfo.get_formatted_resolution_list(),
width=20,
state="readonly",
)
sdr_timing_combo.bind("<<ComboboxSelected>>", self.on_sdr_timing_changed)
sdr_timing_combo.grid(row=4, column=1, sticky=tk.W, padx=5, pady=2)
# 色彩格式
ttk.Label(self.sdr_signal_frame, text="色彩格式:").grid(
row=5, column=0, sticky=tk.W, padx=5, pady=2
)
self.sdr_output_format_var = tk.StringVar(value=UCDEnum.SignalFormat.OutputFormat.RGB)
sdr_output_format_combo = ttk.Combobox(
self.sdr_signal_frame,
textvariable=self.sdr_output_format_var,
values=UCDEnum.SignalFormat.OutputFormat.get_list(),
width=10,
state="readonly",
)
sdr_output_format_combo.bind("<<ComboboxSelected>>", self.on_sdr_output_format_changed)
sdr_output_format_combo.grid(row=5, column=1, sticky=tk.W, padx=5, pady=2)
2026-04-20 11:48:38 +08:00
# ==================== HDR信号格式设置 ====================
self.hdr_signal_frame = ttk.Frame(self.signal_tabs)
# 配置列权重
self.hdr_signal_frame.grid_columnconfigure(0, weight=0)
self.hdr_signal_frame.grid_columnconfigure(1, weight=1)
self.signal_tabs.add(self.hdr_signal_frame, text="HDR")
# 色彩空间
ttk.Label(self.hdr_signal_frame, text="色彩空间:").grid(
row=0, column=0, sticky=tk.W, padx=5, pady=2
)
self.hdr_color_space_var = tk.StringVar(value="BT.2020")
hdr_color_space_combo = ttk.Combobox(
self.hdr_signal_frame,
textvariable=self.hdr_color_space_var,
values=["BT.2020", "DCI-P3"],
width=10,
state="readonly",
)
hdr_color_space_combo.grid(row=0, column=1, sticky=tk.W, padx=5, pady=2)
# Metadata设置
ttk.Label(self.hdr_signal_frame, text="Metadata:").grid(
row=1, column=0, sticky=tk.W, padx=5, pady=2
)
self.hdr_metadata_frame = ttk.Frame(self.hdr_signal_frame)
self.hdr_metadata_frame.grid(
row=1, column=1, rowspan=2, sticky=tk.W, padx=5, pady=2
)
ttk.Label(self.hdr_metadata_frame, text="MaxCLL:").grid(
row=0, column=0, sticky=tk.W
)
self.hdr_maxcll_var = tk.StringVar(value="1000")
ttk.Entry(
self.hdr_metadata_frame, textvariable=self.hdr_maxcll_var, width=6
).grid(row=0, column=1, padx=2)
ttk.Label(self.hdr_metadata_frame, text="MaxFALL:").grid(
row=1, column=0, sticky=tk.W
)
self.hdr_maxfall_var = tk.StringVar(value="400")
ttk.Entry(
self.hdr_metadata_frame, textvariable=self.hdr_maxfall_var, width=6
).grid(row=1, column=1, padx=2)
# 数据范围
ttk.Label(self.hdr_signal_frame, text="数据范围:").grid(
row=3, column=0, sticky=tk.W, padx=5, pady=2
)
2026-05-22 11:31:36 +08:00
self.hdr_data_range_var = tk.StringVar(value=UCDEnum.SignalFormat.DataRange.FULL)
2026-04-20 11:48:38 +08:00
hdr_range_combo = ttk.Combobox(
self.hdr_signal_frame,
textvariable=self.hdr_data_range_var,
2026-05-22 11:31:36 +08:00
values=UCDEnum.SignalFormat.DataRange.get_list(),
2026-04-20 11:48:38 +08:00
width=10,
state="readonly",
)
hdr_range_combo.grid(row=3, column=1, sticky=tk.W, padx=5, pady=2)
# 编码位深
ttk.Label(self.hdr_signal_frame, text="编码位深:").grid(
row=4, column=0, sticky=tk.W, padx=5, pady=2
)
2026-05-22 11:31:36 +08:00
self.hdr_bit_depth_var = tk.StringVar(value=UCDEnum.SignalFormat.BitDepth.BIT_8)
2026-04-20 11:48:38 +08:00
hdr_bit_depth_combo = ttk.Combobox(
self.hdr_signal_frame,
textvariable=self.hdr_bit_depth_var,
2026-05-22 11:31:36 +08:00
values=UCDEnum.SignalFormat.BitDepth.get_list(),
2026-04-20 11:48:38 +08:00
width=10,
state="readonly",
)
hdr_bit_depth_combo.grid(row=4, column=1, sticky=tk.W, padx=5, pady=2)
2026-05-22 11:31:36 +08:00
# 色彩格式
ttk.Label(self.hdr_signal_frame, text="色彩格式:").grid(
row=5, column=0, sticky=tk.W, padx=5, pady=2
)
self.hdr_output_format_var = tk.StringVar(value=UCDEnum.SignalFormat.OutputFormat.RGB)
hdr_output_format_combo = ttk.Combobox(
self.hdr_signal_frame,
textvariable=self.hdr_output_format_var,
values=UCDEnum.SignalFormat.OutputFormat.get_list(),
width=10,
state="readonly",
)
hdr_output_format_combo.bind("<<ComboboxSelected>>", self.on_hdr_output_format_changed)
hdr_output_format_combo.grid(row=5, column=1, sticky=tk.W, padx=5, pady=2)
2026-04-20 11:48:38 +08:00
# ==================== 初始化:默认只启用屏模组 Tab ====================
self.signal_tabs.select(0) # 选中屏模组
self.signal_tabs.tab(1, state="disabled") # 禁用 SDR
self.signal_tabs.tab(2, state="disabled") # 禁用 HDR
def create_connection_content(self: "PQAutomationApp"):
2026-04-20 11:48:38 +08:00
"""创建设备连接区域"""
# 创建设备连接区域的主框架
com_frame = ttk.Frame(self.connection_frame)
com_frame.pack(fill=tk.X, pady=5)
2026-05-22 11:31:36 +08:00
# 获取可用的COM端口列表和UCD设备列表
2026-04-20 11:48:38 +08:00
available_ports = self.get_available_com_ports()
2026-05-22 11:31:36 +08:00
available_ucd_list = self.get_available_ucd_ports()
2026-04-20 11:48:38 +08:00
# 使用网格布局,更整齐
ttk.Label(com_frame, text="UCD列表:").grid(
row=0, column=0, sticky=ttk.W, padx=5, pady=3
)
self.ucd_list_var = tk.StringVar(value=self.config.device_config["ucd_list"])
self.ucd_list_combo = ttk.Combobox(
com_frame,
textvariable=self.ucd_list_var,
2026-05-22 11:31:36 +08:00
values=available_ucd_list,
2026-04-20 11:48:38 +08:00
width=10,
state="readonly",
)
self.ucd_list_combo.grid(row=0, column=1, sticky=ttk.W, padx=5, pady=3)
self.ucd_list_combo.bind("<<ComboboxSelected>>", self.update_config)
# 添加UCD连接状态指示器
self.ucd_status_indicator = tk.Canvas(
com_frame, width=15, height=15, bg="gray", highlightthickness=0
)
self.ucd_status_indicator.grid(row=0, column=2, padx=(10, 20))
self.ucd_status_indicator.config(bg="gray")
# 添加按钮框架
button_frame = ttk.Frame(com_frame)
button_frame.grid(row=3, column=0, columnspan=3, pady=3, sticky="w")
connect_icon = load_icon("assets/connect-svgrepo-com.png")
self.check_button = ttk.Button(
button_frame,
image=connect_icon,
bootstyle="link",
takefocus=False,
command=self.check_com_connections,
)
self.check_button.image = connect_icon
self.check_button.pack(side="left", padx=0, pady=3)
disconnect_icon = load_icon("assets/disconnect-svgrepo-com.png")
# 断开连接按钮
self.disconnect_button = ttk.Button(
button_frame,
image=disconnect_icon,
bootstyle="link",
takefocus=False,
command=self.disconnect_com_connections,
)
self.disconnect_button.image = disconnect_icon # 防止图标被垃圾回收
self.disconnect_button.pack(side="left", padx=0, pady=3)
refresh_icon = load_icon("assets/refresh-svgrepo-com.png")
self.refresh_button = ttk.Button(
button_frame,
image=refresh_icon,
bootstyle="link",
takefocus=False,
command=self.refresh_com_ports,
)
self.refresh_button.image = refresh_icon # 防止图标被垃圾回收
self.refresh_button.pack(side="left", padx=0, pady=3)
# CA端口
ttk.Label(com_frame, text="CA端口:").grid(
row=1, column=0, sticky=ttk.W, padx=5, pady=3
)
self.ca_com_var = tk.StringVar(value=self.config.device_config["ca_com"])
self.ca_com_combo = ttk.Combobox(
com_frame,
textvariable=self.ca_com_var,
values=available_ports,
width=10,
state="readonly",
)
self.ca_com_combo.grid(row=1, column=1, sticky=ttk.W, padx=5, pady=3)
self.ca_com_combo.bind("<<ComboboxSelected>>", self.update_config)
# 添加CA连接状态指示器
self.ca_status_indicator = tk.Canvas(
com_frame, width=15, height=15, bg="gray", highlightthickness=0
)
self.ca_status_indicator.grid(row=1, column=2, padx=(10, 20))
self.ca_status_indicator.config(bg="gray")
# 添加CA通道设置
ttk.Label(com_frame, text="CA通道:").grid(
row=2, column=0, sticky=tk.W, padx=5, pady=3
)
self.ca_channel_var = tk.StringVar(
value=self.config.device_config["ca_channel"]
)
ca_channel_combo = ttk.Combobox(
com_frame,
textvariable=self.ca_channel_var,
values=[str(i) for i in range(11)],
width=10,
state="readonly",
)
ca_channel_combo.grid(row=2, column=1, sticky=ttk.W, padx=5, pady=3)
ca_channel_combo.bind("<<ComboboxSelected>>", self.update_config)
def create_test_type_frame(self: "PQAutomationApp"):
2026-05-28 10:50:52 +08:00
"""创建测试类型选择区域(侧边栏形式)。
新版v3改进
- 深灰分层背景接近 Calman 的侧栏密度
- 纯文字按钮不使用 emoji
- 用更克制的字号 / 间距做层级区分
- 不再使用 padding=10 硬覆盖交给 Sidebar.TButton 样式统一管理
"""
2026-04-20 11:48:38 +08:00
# 设置测试类型变量
self.test_type_var = tk.StringVar(value="screen_module")
2026-05-28 10:50:52 +08:00
# ---------- 顶部品牌区 ----------
brand = ttk.Frame(self.sidebar_frame, style="SidebarBrand.TFrame")
brand.pack(fill=tk.X, pady=(2, 10))
ttk.Label(
brand,
text="PQ AUTOMATION",
style="SidebarBrand.TLabel",
).pack(side=tk.LEFT, padx=16, pady=11)
# ---------- 分组:测试类型 ----------
ttk.Label(
self.sidebar_frame,
text="测试类型",
style="SidebarSection.TLabel",
).pack(fill=tk.X, padx=16, pady=(2, 6), anchor="w")
2026-04-20 11:48:38 +08:00
test_types = [
("屏模组性能测试", "screen_module"),
2026-05-28 10:50:52 +08:00
("SDR Movie", "sdr_movie"),
("HDR Movie", "hdr_movie"),
2026-04-20 11:48:38 +08:00
]
for text, type_value in test_types:
btn = ttk.Button(
master=self.sidebar_frame,
text=text,
style="Sidebar.TButton",
command=lambda v=type_value: self.change_test_type(v),
takefocus=False,
)
btn.pack(fill=tk.X, padx=0, pady=1)
setattr(self, f"{type_value}_btn", btn)
2026-05-28 10:50:52 +08:00
# ---------- 分组:工具面板 ----------
ttk.Label(
self.sidebar_frame,
2026-05-28 10:50:52 +08:00
text="工具面板",
style="SidebarSection.TLabel",
).pack(fill=tk.X, padx=16, pady=(16, 6), anchor="w")
panel_buttons = [
("log_btn", "测试日志", self.toggle_log_panel),
("local_dimming_btn", "Local Dimming", self.toggle_local_dimming_panel),
("ai_image_btn", "AI 图片", self.toggle_ai_image_panel),
("pantone_baseline_btn", "Pantone 摸底", self.toggle_pantone_baseline_panel),
("gamma_pattern_btn", "Gamma 图案", self.toggle_gamma_pattern_panel),
("calman_btn", "CALMAN 灰阶", self.toggle_calman_panel),
]
for attr, text, cmd in panel_buttons:
btn = ttk.Button(
self.sidebar_frame,
text=text,
style="Sidebar.TButton",
command=cmd,
takefocus=False,
)
btn.pack(fill=tk.X, padx=0, pady=1)
setattr(self, attr, btn)
2026-05-22 11:31:36 +08:00
# 测试版水印标签(版本 x.x.0.0 时显示)
from app_version import is_beta_version, APP_VERSION
if is_beta_version():
beta_lbl = tk.Label(
self.sidebar_frame,
text=f"[测试版] v{APP_VERSION}",
foreground="#ffffff",
background="#cc3300",
font=("微软雅黑", 8, "bold"),
anchor="center",
)
beta_lbl.pack(fill=tk.X, side=tk.BOTTOM, padx=4, pady=(6, 4))
2026-05-28 10:50:52 +08:00
# ---------- 主题切换(底部固定) ----------
self.theme_toggle_btn = ttk.Button(
self.sidebar_frame,
text="切换深色模式",
style="Sidebar.TButton",
command=self._on_toggle_theme,
takefocus=False,
)
self.theme_toggle_btn.pack(fill=tk.X, padx=0, pady=(0, 2), side=tk.BOTTOM)
_refresh_theme_toggle_label(self)
2026-04-21 14:06:48 +08:00
# 注册面板按钮
2026-04-20 11:48:38 +08:00
if hasattr(self, "panels"):
if "log" in self.panels:
self.panels["log"]["button"] = self.log_btn
if "local_dimming" in self.panels:
self.panels["local_dimming"]["button"] = self.local_dimming_btn
2026-04-21 14:06:48 +08:00
if "ai_image" in self.panels:
self.panels["ai_image"]["button"] = self.ai_image_btn
if "single_step" in self.panels:
2026-05-28 10:50:52 +08:00
self.panels["single_step"]["button"] = getattr(self, "single_step_btn", None)
if "pantone_baseline" in self.panels:
self.panels["pantone_baseline"]["button"] = self.pantone_baseline_btn
if "gamma_pattern" in self.panels:
self.panels["gamma_pattern"]["button"] = self.gamma_pattern_btn
if "calman" in self.panels:
self.panels["calman"]["button"] = self.calman_btn
2026-04-20 11:48:38 +08:00
2026-05-28 10:50:52 +08:00
def _refresh_theme_toggle_label(self: "PQAutomationApp") -> None:
"""根据当前主题刷新切换按钮的文字。"""
from app.views.theme_manager import is_dark
if not hasattr(self, "theme_toggle_btn"):
return
if is_dark():
self.theme_toggle_btn.configure(text="切换浅色模式")
else:
self.theme_toggle_btn.configure(text="切换深色模式")
def _on_toggle_theme(self: "PQAutomationApp") -> None:
"""切换主题:重新应用 ttk 样式并刷新所有自定义样式相关的标签。"""
from app.views.theme_manager import toggle_theme
toggle_theme()
_refresh_theme_toggle_label(self)
# 同步刷新侧栏选中态(高亮样式跟随新色板)
if hasattr(self, "update_sidebar_selection"):
try:
self.update_sidebar_selection()
except Exception:
pass
def update_config_info_display(self: "PQAutomationApp"):
2026-04-20 11:48:38 +08:00
"""更新配置信息显示"""
if hasattr(self, "config") and hasattr(self.config, "get_current_config"):
current_config = self.config.get_current_config()
info_text = f"测试类型: {current_config.get('name', '未知')}\n"
info_text += (
f"测试项目: {', '.join(current_config.get('test_items', []))}\n"
)
info_text += f"信号格式: {current_config.get('signal_format', 'none')}\n"
info_text += f"色彩空间: {current_config.get('color_space', 'unknown')}\n"
info_text += f"位深度: {current_config.get('bit_depth', 'unknown')}"
# 高亮当前选中的测试类型
self.update_sidebar_selection()
def create_operation_frame(self: "PQAutomationApp"):
"""创建操作按钮区域。
新布局按钮挂到配置项 header 右侧常驻工具条 ``self.toolbar_actions_frame``
若该容器尚未创建极端情况下顺序异常回退到原 control_frame_top 位置
确保按钮始终可见调用方代码不破
"""
parent = getattr(self, "toolbar_actions_frame", None)
fallback = parent is None
if fallback:
parent = ttk.Frame(self.control_frame_top)
parent.pack(fill=tk.X, padx=5, pady=8)
# 用 bootstyle 取代旧 style="xxx.TButton",跟随主题;
# 给按钮统一加 padding触摸友好。
btn_pad = dict(padx=4, pady=0)
2026-04-20 11:48:38 +08:00
self.start_btn = ttk.Button(
parent,
text="\u25b6 开始测试",
2026-04-20 11:48:38 +08:00
command=self.start_test,
bootstyle="success",
padding=(12, 6),
takefocus=False,
2026-04-20 11:48:38 +08:00
)
self.start_btn.pack(side=tk.LEFT, **btn_pad)
2026-04-20 11:48:38 +08:00
self.simulate_btn = ttk.Button(
parent,
text="模拟测试",
command=self.run_simulation_test,
bootstyle="warning-outline",
padding=(12, 6),
takefocus=False,
)
self.simulate_btn.pack(side=tk.LEFT, **btn_pad)
2026-04-20 11:48:38 +08:00
self.stop_btn = ttk.Button(
parent,
text="\u25a0 停止",
2026-04-20 11:48:38 +08:00
command=self.stop_test,
bootstyle="danger",
padding=(12, 6),
2026-04-20 11:48:38 +08:00
state=tk.DISABLED,
takefocus=False,
2026-04-20 11:48:38 +08:00
)
self.stop_btn.pack(side=tk.LEFT, **btn_pad)
# 分隔
ttk.Separator(parent, orient="vertical").pack(side=tk.LEFT, fill="y", padx=8, pady=4)
2026-04-20 11:48:38 +08:00
self.save_btn = ttk.Button(
parent,
2026-04-20 11:48:38 +08:00
text="保存结果",
command=self.save_results,
bootstyle="info-outline",
padding=(12, 6),
2026-04-20 11:48:38 +08:00
state=tk.DISABLED,
takefocus=False,
)
self.save_btn.pack(side=tk.LEFT, **btn_pad)
self.custom_btn = ttk.Button(
parent,
text="客户模版",
command=self.start_custom_template_test,
bootstyle="info",
padding=(12, 6),
takefocus=False,
2026-04-20 11:48:38 +08:00
)
self.custom_btn.pack(side=tk.LEFT, **btn_pad)
2026-04-20 11:48:38 +08:00
self.clear_config_btn = ttk.Button(
parent,
2026-04-20 11:48:38 +08:00
text="清理配置",
command=self.clear_config_file,
bootstyle="secondary-outline",
padding=(10, 6),
takefocus=False,
2026-04-20 11:48:38 +08:00
)
self.clear_config_btn.pack(side=tk.LEFT, **btn_pad)
2026-04-20 11:48:38 +08:00
self.update_custom_button_visibility()
def on_screen_module_timing_changed(self: "PQAutomationApp", event=None):
"""屏模组信号格式改变时的回调"""
try:
selected_timing = self.screen_module_timing_var.get()
# 记录日志
2026-04-21 15:31:48 +08:00
self.log_gui.log(f"屏模组信号格式已更改为: {selected_timing}", level="info")
match = re.search(r"(\d+)x(\d+)\s*@\s*(\d+)", selected_timing)
if match:
width = int(match.group(1))
height = int(match.group(2))
refresh_rate = int(match.group(3))
2026-04-21 15:31:48 +08:00
self.log_gui.log(f" ├─ 分辨率: {width}x{height}", level="info")
self.log_gui.log(f" └─ 刷新率: {refresh_rate}Hz", level="info")
# 根据分辨率给出提示
if width >= 3840: # 4K及以上
2026-04-21 15:31:48 +08:00
self.log_gui.log(" 检测到4K分辨率", level="info")
if refresh_rate >= 120:
2026-04-21 15:31:48 +08:00
self.log_gui.log(" 检测到高刷新率", level="info")
# 更新屏模组配置(独立于 current_test_type
self.config.current_test_types.setdefault("screen_module", {})[
"timing"
] = selected_timing
# 如果正在测试,提示用户
if self.testing:
2026-04-21 15:31:48 +08:00
self.log_gui.log("警告: 测试进行中,信号格式更改将在下次测试时生效", level="error")
# 保存配置
self.save_pq_config()
except Exception as e:
2026-04-21 16:03:11 +08:00
self.log_gui.log(f"屏模组信号格式更改失败: {str(e)}", level="error")
2026-04-21 11:50:57 +08:00
def on_screen_module_signal_format_changed(self: "PQAutomationApp", event=None):
"""屏模组 ColorInfo 相关选项变更回调。"""
try:
color_space = self.screen_module_color_space_var.get()
data_range = self.screen_module_data_range_var.get()
bit_depth = self.screen_module_bit_depth_var.get()
output_format = self.screen_module_output_format_var.get()
screen_cfg = self.config.current_test_types.setdefault("screen_module", {})
screen_cfg["colorimetry"] = color_space
screen_cfg["color_format"] = output_format
screen_cfg["bpc"] = UCDEnum.SignalFormat.BitDepth.get_bit_value(bit_depth)
screen_cfg["data_range"] = data_range
self.log_gui.log(
(
"屏模组信号格式已更新: "
f"色彩空间={color_space}, 数据范围={data_range}, "
f"位深={bit_depth}, 色彩格式={output_format}"
),
level="info",
)
if self.testing:
self.log_gui.log("警告: 测试进行中,格式更改将在下次测试时生效", level="error")
self.save_pq_config()
return
if getattr(self.ucd, "status", False):
ok = self.signal_service.update_signal_format(
color_space=color_space,
data_range=data_range,
bit_depth=bit_depth,
output_format=output_format,
)
if not ok:
self.log_gui.log("屏模组信号格式应用到UCD失败", level="error")
self.save_pq_config()
except Exception as e:
self.log_gui.log(f"屏模组信号格式更改失败: {str(e)}", level="error")
def on_sdr_timing_changed(self: "PQAutomationApp", event=None):
2026-05-22 11:31:36 +08:00
"""SDR测试分辨率改变时的回调"""
try:
selected_timing = self.sdr_timing_var.get()
self.log_gui.log(f"SDR测试分辨率已更改为: {selected_timing}", level="info")
# 直接更新 sdr_movie 的 timing 配置
self.config.current_test_types["sdr_movie"]["timing"] = selected_timing
if self.testing:
self.log_gui.log("警告: 测试进行中,分辨率更改将在下次测试时生效", level="error")
self.save_pq_config()
except Exception as e:
self.log_gui.log(f"SDR测试分辨率更改失败: {str(e)}", level="error")
def on_sdr_output_format_changed(self: "PQAutomationApp", event=None):
2026-05-22 11:31:36 +08:00
"""SDR 色彩格式改变时的回调"""
try:
fmt = self.sdr_output_format_var.get()
self.log_gui.log(f"SDR色彩格式已更改为: {fmt}", level="info")
if self.testing:
self.log_gui.log("警告: 测试进行中,格式更改将在下次测试时生效", level="error")
return
if getattr(self.ucd, "status", False):
2026-05-24 11:02:37 +08:00
ok = self.signal_service.update_signal_format(
2026-05-22 11:31:36 +08:00
color_space=self.sdr_color_space_var.get(),
data_range=self.sdr_data_range_var.get(),
bit_depth=self.sdr_bit_depth_var.get(),
2026-05-24 11:02:37 +08:00
output_format=fmt,
2026-05-22 11:31:36 +08:00
)
if not ok:
self.log_gui.log("SDR色彩格式应用到UCD失败", level="error")
except Exception as e:
self.log_gui.log(f"SDR色彩格式更改失败: {str(e)}", level="error")
def on_hdr_output_format_changed(self: "PQAutomationApp", event=None):
2026-05-22 11:31:36 +08:00
"""HDR 色彩格式改变时的回调"""
try:
fmt = self.hdr_output_format_var.get()
self.log_gui.log(f"HDR色彩格式已更改为: {fmt}", level="info")
if self.testing:
self.log_gui.log("警告: 测试进行中,格式更改将在下次测试时生效", level="error")
return
if getattr(self.ucd, "status", False):
2026-05-24 11:02:37 +08:00
ok = self.signal_service.update_signal_format(
2026-05-22 11:31:36 +08:00
color_space=self.hdr_color_space_var.get(),
data_range=self.hdr_data_range_var.get(),
bit_depth=self.hdr_bit_depth_var.get(),
max_cll=self.hdr_maxcll_var.get(),
max_fall=self.hdr_maxfall_var.get(),
2026-05-24 11:02:37 +08:00
output_format=fmt,
2026-05-22 11:31:36 +08:00
)
if not ok:
self.log_gui.log("HDR色彩格式应用到UCD失败", level="error")
except Exception as e:
self.log_gui.log(f"HDR色彩格式更改失败: {str(e)}", level="error")
def update_test_items(self: "PQAutomationApp"):
2026-04-21 11:50:57 +08:00
"""根据当前测试类型更新测试项目复选框"""
# 先隐藏所有测试项目框架
for config in self.test_items.values():
config["frame"].pack_forget()
current_test_type = self.config.current_test_type
self.test_vars = {}
if current_test_type in self.test_items:
config = self.test_items[current_test_type]
frame = config["frame"]
frame.pack(fill=tk.X, padx=5, pady=5)
# 添加测试类型标签
type_label = ttk.Label(
frame,
text=self.get_test_type_name(current_test_type),
style="primary.TLabel",
)
type_label.grid(row=0, column=0, columnspan=2, sticky=tk.W, padx=5, pady=3)
# 从配置中读取保存的选择状态
saved_test_items = self.config.current_test_types[current_test_type].get(
"test_items", []
)
# 添加复选框
for i, (text, var_name) in enumerate(config["items"]):
is_checked = var_name in saved_test_items
var = tk.BooleanVar(value=is_checked)
self.test_vars[f"{current_test_type}_{var_name}"] = var
ttk.Checkbutton(
frame,
text=text,
variable=var,
bootstyle="round-toggle",
command=self.update_config_and_tabs,
).grid(row=i // 2 + 1, column=i % 2, sticky=tk.W, padx=10, pady=5)
if hasattr(self, "chart_notebook"):
self.update_chart_tabs_state()
if hasattr(self, "cct_params_frame"):
self.toggle_cct_params_frame()
# 同步刷新 header 折叠预览
refresh_config_preview(self)
2026-04-21 11:50:57 +08:00
def on_test_type_change(self: "PQAutomationApp"):
2026-04-21 11:50:57 +08:00
"""根据测试类型更新内容区域"""
# 更新配置信息显示
if hasattr(self, "config") and hasattr(self.config, "get_current_config"):
self.update_config_info_display()
# SDR 选中时显示客户模版按钮
self.update_custom_button_visibility()
class MainLayoutMixin:
"""由 tools/refactor_to_mixins.py 自动生成。
把本模块的自由函数挂到 PQAutomationApp 便于 F12 跳转与类型推断
"""
create_floating_config_panel = create_floating_config_panel
refresh_config_preview = refresh_config_preview
create_test_items_content = create_test_items_content
create_signal_format_content = create_signal_format_content
create_connection_content = create_connection_content
create_test_type_frame = create_test_type_frame
update_config_info_display = update_config_info_display
create_operation_frame = create_operation_frame
2026-05-28 10:50:52 +08:00
_on_toggle_theme = _on_toggle_theme
on_screen_module_timing_changed = on_screen_module_timing_changed
on_screen_module_signal_format_changed = on_screen_module_signal_format_changed
on_sdr_timing_changed = on_sdr_timing_changed
on_sdr_output_format_changed = on_sdr_output_format_changed
on_hdr_output_format_changed = on_hdr_output_format_changed
update_test_items = update_test_items
on_test_type_change = on_test_type_change