"""主布局面板创建函数(Step 6 重构)。""" import re 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 样式。 """ cf = CollapsingFrame(self.control_frame_top) cf.pack(fill="both") self._config_collapsing = cf # 配置项主体容器(卡片宿主) 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, ) # 卡片三栏 self.config_content_frame = ttk.Frame(self.config_panel_frame) self.config_content_frame.pack(fill=tk.BOTH, expand=True, padx=8, pady=8) config_row_frame = ttk.Frame(self.config_content_frame) config_row_frame.pack(fill=tk.X, expand=False) # 设备连接 卡片 connection_card = _make_card(config_row_frame, icon="\U0001F4E1", 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] # 测试项目 卡片 test_items_card = _make_card(config_row_frame, icon="\u2714", 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] # 信号格式 卡片 signal_card = _make_card(config_row_frame, icon="\u2699", 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] # 创建卡片内部内容(沿用旧函数,父级已是 body Frame) self.create_connection_content() self.create_test_items_content() self.create_signal_format_content() # 默认收起 —— 与旧版行为保持一致 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)) def create_test_items_content(self: "PQAutomationApp"): """创建测试项目选项卡内容""" # 创建测试项目字典,用于管理不同测试类型的选项 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"): """创建信号格式选项卡内容""" 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) self.signal_tabs.add(self.screen_module_signal_frame, text="屏模组测试") screen_cfg = self.config.current_test_types.get("screen_module", {}) 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 ) 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( "<>", 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( "<>", 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( "<>", 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( "<>", 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( "<>", self.on_screen_module_signal_format_changed ) screen_module_output_format_combo.grid(row=4, column=1, sticky=tk.W, padx=5, pady=2) # ==================== 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, values=["sRGB", "BT.709", "BT.601", "BT.2020", "DCI-P3"], width=10, state="readonly", ) sdr_color_space_combo.grid(row=0, column=1, sticky=tk.W, padx=5, pady=2) # Gamma(测试参考值,用于Gamma曲线绘制和色准计算) ttk.Label(self.sdr_signal_frame, text="Gamma:").grid( row=1, column=0, sticky=tk.W, padx=5, pady=2 ) self.sdr_gamma_type_var = tk.StringVar(value=UCDEnum.SignalFormat.GammaType.GAMMA_22) sdr_gamma_combo = ttk.Combobox( self.sdr_signal_frame, textvariable=self.sdr_gamma_type_var, values=UCDEnum.SignalFormat.GammaType.get_list(), 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 ) self.sdr_data_range_var = tk.StringVar(value=UCDEnum.SignalFormat.DataRange.FULL) sdr_range_combo = ttk.Combobox( self.sdr_signal_frame, textvariable=self.sdr_data_range_var, values=UCDEnum.SignalFormat.DataRange.get_list(), 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 ) self.sdr_bit_depth_var = tk.StringVar(value=UCDEnum.SignalFormat.BitDepth.BIT_8) sdr_bit_depth_combo = ttk.Combobox( self.sdr_signal_frame, textvariable=self.sdr_bit_depth_var, values=UCDEnum.SignalFormat.BitDepth.get_list(), width=10, state="readonly", ) sdr_bit_depth_combo.grid(row=3, column=1, sticky=tk.W, padx=5, pady=2) # 分辨率 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("<>", 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("<>", self.on_sdr_output_format_changed) sdr_output_format_combo.grid(row=5, column=1, sticky=tk.W, padx=5, pady=2) # ==================== 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 ) self.hdr_data_range_var = tk.StringVar(value=UCDEnum.SignalFormat.DataRange.FULL) hdr_range_combo = ttk.Combobox( self.hdr_signal_frame, textvariable=self.hdr_data_range_var, values=UCDEnum.SignalFormat.DataRange.get_list(), 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 ) self.hdr_bit_depth_var = tk.StringVar(value=UCDEnum.SignalFormat.BitDepth.BIT_8) hdr_bit_depth_combo = ttk.Combobox( self.hdr_signal_frame, textvariable=self.hdr_bit_depth_var, values=UCDEnum.SignalFormat.BitDepth.get_list(), width=10, state="readonly", ) hdr_bit_depth_combo.grid(row=4, column=1, sticky=tk.W, padx=5, pady=2) # 色彩格式 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("<>", self.on_hdr_output_format_changed) hdr_output_format_combo.grid(row=5, column=1, sticky=tk.W, padx=5, pady=2) # ==================== 初始化:默认只启用屏模组 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"): """创建设备连接区域""" # 创建设备连接区域的主框架 com_frame = ttk.Frame(self.connection_frame) com_frame.pack(fill=tk.X, pady=5) # 获取可用的COM端口列表和UCD设备列表 available_ports = self.get_available_com_ports() available_ucd_list = self.get_available_ucd_ports() # 使用网格布局,更整齐 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, values=available_ucd_list, 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("<>", 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("<>", 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("<>", self.update_config) def create_test_type_frame(self: "PQAutomationApp"): """创建测试类型选择区域(侧边栏形式)""" # 设置测试类型变量 self.test_type_var = tk.StringVar(value="screen_module") # 创建测试类型按钮并放置在侧边栏 test_types = [ ("屏模组性能测试", "screen_module"), ("SDR Movie测试", "sdr_movie"), ("HDR Movie测试", "hdr_movie"), ] for text, type_value in test_types: btn = ttk.Button( master=self.sidebar_frame, text=text, style="Sidebar.TButton", padding=10, 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) # 添加分隔线 ttk.Separator(self.sidebar_frame, orient="horizontal").pack( fill=tk.X, padx=10, pady=10 ) # 只保留日志按钮 self.log_btn = ttk.Button( self.sidebar_frame, text="测试日志", style="Sidebar.TButton", command=self.toggle_log_panel, takefocus=False, ) self.log_btn.pack(fill=tk.X, padx=0, pady=1) # Local Dimming 测试按钮 self.local_dimming_btn = ttk.Button( self.sidebar_frame, text="Local Dimming", style="Sidebar.TButton", command=self.toggle_local_dimming_panel, takefocus=False, ) self.local_dimming_btn.pack(fill=tk.X, padx=0, pady=1) # AI 图片对话按钮 self.ai_image_btn = ttk.Button( self.sidebar_frame, text="AI 图片", style="Sidebar.TButton", command=self.toggle_ai_image_panel, takefocus=False, ) self.ai_image_btn.pack(fill=tk.X, padx=0, pady=1) # self.single_step_btn = ttk.Button( # self.sidebar_frame, # text="单步调试", # style="Sidebar.TButton", # command=self.toggle_single_step_panel, # takefocus=False, # ) # self.single_step_btn.pack(fill=tk.X, padx=0, pady=1) self.pantone_baseline_btn = ttk.Button( self.sidebar_frame, text="Pantone认证摸底测试", style="Sidebar.TButton", command=self.toggle_pantone_baseline_panel, takefocus=False, ) self.pantone_baseline_btn.pack(fill=tk.X, padx=0, pady=1) # Gamma 测试图案配置按钮 self.gamma_pattern_btn = ttk.Button( self.sidebar_frame, text="Gamma 图案配置", style="Sidebar.TButton", command=self.toggle_gamma_pattern_panel, takefocus=False, ) self.gamma_pattern_btn.pack(fill=tk.X, padx=0, pady=1) # CALMAN 风格灰阶测试按钮 self.calman_btn = ttk.Button( self.sidebar_frame, text="CALMAN 灰阶", style="Sidebar.TButton", command=self.toggle_calman_panel, takefocus=False, ) self.calman_btn.pack(fill=tk.X, padx=0, pady=1) # 测试版水印标签(版本 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)) # 注册面板按钮 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 if "ai_image" in self.panels: self.panels["ai_image"]["button"] = self.ai_image_btn if "single_step" in self.panels: self.panels["single_step"]["button"] = self.single_step_btn 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 def update_config_info_display(self: "PQAutomationApp"): """更新配置信息显示""" 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) self.start_btn = ttk.Button( parent, text="\u25b6 开始测试", command=self.start_test, bootstyle="success", padding=(12, 6), takefocus=False, ) self.start_btn.pack(side=tk.LEFT, **btn_pad) 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) self.stop_btn = ttk.Button( parent, text="\u25a0 停止", command=self.stop_test, bootstyle="danger", padding=(12, 6), state=tk.DISABLED, takefocus=False, ) self.stop_btn.pack(side=tk.LEFT, **btn_pad) # 分隔 ttk.Separator(parent, orient="vertical").pack(side=tk.LEFT, fill="y", padx=8, pady=4) self.save_btn = ttk.Button( parent, text="保存结果", command=self.save_results, bootstyle="info-outline", padding=(12, 6), 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, ) self.custom_btn.pack(side=tk.LEFT, **btn_pad) self.clear_config_btn = ttk.Button( parent, text="清理配置", command=self.clear_config_file, bootstyle="secondary-outline", padding=(10, 6), takefocus=False, ) self.clear_config_btn.pack(side=tk.LEFT, **btn_pad) self.update_custom_button_visibility() def on_screen_module_timing_changed(self: "PQAutomationApp", event=None): """屏模组信号格式改变时的回调""" try: selected_timing = self.screen_module_timing_var.get() # 记录日志 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)) self.log_gui.log(f" ├─ 分辨率: {width}x{height}", level="info") self.log_gui.log(f" └─ 刷新率: {refresh_rate}Hz", level="info") # 根据分辨率给出提示 if width >= 3840: # 4K及以上 self.log_gui.log(" ℹ️ 检测到4K分辨率", level="info") if refresh_rate >= 120: self.log_gui.log(" ℹ️ 检测到高刷新率", level="info") # 更新屏模组配置(独立于 current_test_type) self.config.current_test_types.setdefault("screen_module", {})[ "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"屏模组信号格式更改失败: {str(e)}", level="error") 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): """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): """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): ok = self.signal_service.update_signal_format( color_space=self.sdr_color_space_var.get(), data_range=self.sdr_data_range_var.get(), bit_depth=self.sdr_bit_depth_var.get(), output_format=fmt, ) 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): """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): ok = self.signal_service.update_signal_format( 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(), output_format=fmt, ) 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"): """根据当前测试类型更新测试项目复选框""" # 先隐藏所有测试项目框架 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) def on_test_type_change(self: "PQAutomationApp"): """根据测试类型更新内容区域""" # 更新配置信息显示 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 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