"""打印队列面板视图
以表格 + 工具栏形式展示打印队列,提供启动/暂停/继续、置顶、上移、
取消、清空等操作的 UI,并通过控制器信号实时刷新(无轮询阻塞)。
仅负责展示与交互,所有业务逻辑委托 PrintQueueController。
"""
from __future__ import annotations
from typing import TYPE_CHECKING
from PySide6.QtWidgets import (
QGroupBox,
QLabel,
QMessageBox,
QTableWidgetItem,
QVBoxLayout,
QWidget,
)
from certflow.utils.logger import logger
from certflow.widgets import ActionToolbar
from certflow.widgets.enhanced_table import EnhancedTable
if TYPE_CHECKING:
from certflow.controllers.print_queue_controller import PrintQueueController
[文档]
class QueuePanel(QGroupBox):
"""打印队列面板
嵌入 PrintView,展示 print_jobs 队列并支持交互操作。
"""
def __init__(self, controller: PrintQueueController, parent: QWidget | None = None) -> None:
"""初始化队列面板
Args:
controller: 打印队列控制器
parent: 父控件
"""
super().__init__("打印队列", parent)
self._controller = controller
# 传输控制条单独构建(启动/暂停/继续/置顶/上移/取消/清空已完成),
# 由 PrintView 提升到顶部作为「主操作面」;QueuePanel 自身只留任务表格与状态。
self._transport_bar = self._build_transport_bar()
self._build_ui()
self._connect_signals()
self._refresh()
def _build_transport_bar(self) -> ActionToolbar:
"""构建传输控制条(启动/暂停/继续/置顶/上移/取消/清空已完成)
仅构建一次并返回控件;信号在此连接。由 PrintView 提升到顶部作主操作面,
QueuePanel 自身不再内置该工具栏(仅保留任务表格与状态标签)。
"""
toolbar = ActionToolbar()
self.btn_start = toolbar.add_button("启动")
self.btn_pause = toolbar.add_button("暂停")
self.btn_resume = toolbar.add_button("继续")
self.btn_top = toolbar.add_button("置顶")
self.btn_up = toolbar.add_button("上移")
self.btn_cancel = toolbar.add_button("取消")
self.btn_clear = toolbar.add_button("清空已完成")
for b in (
self.btn_start,
self.btn_pause,
self.btn_resume,
self.btn_top,
self.btn_up,
self.btn_cancel,
self.btn_clear,
):
b.setMinimumHeight(26)
toolbar.add_stretch()
self.btn_start.clicked.connect(self._on_start)
self.btn_pause.clicked.connect(self._on_pause)
self.btn_resume.clicked.connect(self._on_resume)
self.btn_top.clicked.connect(self._on_top)
self.btn_up.clicked.connect(self._on_up)
self.btn_cancel.clicked.connect(self._on_cancel)
self.btn_clear.clicked.connect(self._on_clear)
return toolbar
@property
def transport_bar(self) -> ActionToolbar:
"""暴露传输控制条,供 PrintView 提升到顶部主操作面。"""
return self._transport_bar
def _build_ui(self) -> None:
"""构建面板 UI(仅任务表格 + 状态;传输控制条已提升至 PrintView 顶部)"""
layout = QVBoxLayout(self)
layout.setContentsMargins(6, 8, 6, 6)
layout.setSpacing(6)
# 状态标签
self.status_label = QLabel("就绪")
self.status_label.setStyleSheet("color: #666; font-size: 11px;")
layout.addWidget(self.status_label)
# 队列表格
self.table = EnhancedTable()
self.table.setColumnCount(5)
self.table.setHorizontalHeaderLabels(["ID", "SN", "状态", "优先级", "创建时间"])
for idx, field in enumerate(["id", "sn", "status", "priority", "created_at"]):
self.table.set_header_field(idx, field)
self.table.setColumnWidth(0, 50)
self.table.setColumnWidth(1, 110)
self.table.setColumnWidth(2, 70)
self.table.setColumnWidth(3, 60)
self.table.setColumnWidth(4, 150)
self.table.setMaximumHeight(220)
# 打印队列是全量本地数据,启用客户端排序
self.table.setSortingEnabled(True)
layout.addWidget(self.table)
def _connect_signals(self) -> None:
"""连接控制器信号,驱动实时刷新"""
self._controller.queue_changed.connect(self._refresh)
self._controller.job_started.connect(lambda _id: self._refresh())
self._controller.job_finished.connect(lambda *_a: self._refresh())
self._controller.status_changed.connect(self.status_label.setText)
# ============================================================
# 刷新
# ============================================================
def _refresh(self) -> None:
"""从控制器拉取队列数据并渲染表格"""
try:
jobs = self._controller.list_jobs(include_terminal=True)
except Exception as e: # noqa: BLE001
logger.warning(f"刷新队列失败: {e}")
return
self.table.setRowCount(len(jobs))
for row, job in enumerate(jobs):
status = job["status"]
self._set_cell(row, 0, str(job["id"]))
self._set_cell(row, 1, job["sn"] or "—")
self._set_cell(
row, 2, self._controller.status_label(status), self._controller.status_color(status)
)
self._set_cell(row, 3, str(job["priority"]))
self._set_cell(row, 4, job["created_at"] or "—")
self.table.resizeColumnsToContents()
def _set_cell(self, row: int, col: int, text: str, color: str | None = None) -> None:
"""设置单元格文本与颜色"""
item = QTableWidgetItem(text)
if color:
from PySide6.QtGui import QColor
item.setForeground(QColor(color))
self.table.setItem(row, col, item)
# ============================================================
# 操作
# ============================================================
def _selected_job_id(self) -> int | None:
"""返回当前选中行的任务 ID
Returns:
int | None: 任务 ID;无选中返回 None
"""
row = self.table.currentRow()
if row < 0:
return None
item = self.table.item(row, 0)
if item is None:
return None
try:
return int(item.text())
except ValueError:
return None
def _on_start(self) -> None:
self._controller.start()
def _on_pause(self) -> None:
self._controller.pause()
def _on_resume(self) -> None:
self._controller.resume()
def _on_top(self) -> None:
job_id = self._selected_job_id()
if job_id is None:
QMessageBox.information(self, "提示", "请先选择一行任务")
return
self._controller.move_top(job_id)
def _on_up(self) -> None:
job_id = self._selected_job_id()
if job_id is None:
QMessageBox.information(self, "提示", "请先选择一行任务")
return
self._controller.move_up(job_id)
def _on_cancel(self) -> None:
job_id = self._selected_job_id()
if job_id is None:
QMessageBox.information(self, "提示", "请先选择一行任务")
return
self._controller.cancel_job(job_id)
def _on_clear(self) -> None:
deleted = self._controller.clear_finished()
self.status_label.setText(f"已清空 {deleted} 条已完成任务")