# -*- coding: utf-8 -*- """ ArrowComboBox - 带动态箭头的下拉框组件 展开时箭头向上,收起时箭头向下 """ import os import re import tempfile from PySide6.QtWidgets import QComboBox from PySide6.QtGui import QPixmap, QPainter, QTransform, QPolygonF from PySide6.QtCore import Qt, Signal, QTimer, QPointF from utils.image_paths import ImagePaths class ArrowComboBox(QComboBox): """带动态箭头的下拉框,展开时箭头向上,收起时箭头向下 Signals: popupShown: 下拉列表展开时发出 popupHidden: 下拉列表收起时发出 arrowDirectionChanged: 箭头方向改变时发出 (is_up: bool) """ # 信号定义 popupShown = Signal() # 下拉列表展开 popupHidden = Signal() # 下拉列表收起 arrowDirectionChanged = Signal(bool) # 箭头方向改变 (True=向上, False=向下) # 类缓存 _arrow_down_path = None # 向下箭头图片路径 _arrow_up_path = None # 向上箭头图片路径 _initialized = False # 是否已初始化 @classmethod def init_arrow_resources(cls, arrow_image_path: str = None, arrow_size: tuple = (14, 8)): """初始化箭头资源(类方法,只需调用一次) Args: arrow_image_path: 自定义箭头图片路径,默认使用 ImagePaths.SYSTEM_DIAGNOSTICS_DROPDOWN_ARROW arrow_size: 箭头尺寸 (width, height),默认 (14, 8) """ if cls._initialized: return arrow_width, arrow_height = arrow_size # 加载原始箭头图片 if arrow_image_path is None: arrow_image_path = ImagePaths.SYSTEM_DIAGNOSTICS_DROPDOWN_ARROW original = QPixmap(arrow_image_path) if original.isNull(): # 如果加载失败,创建默认箭头 original = QPixmap(arrow_width, arrow_height) original.fill(Qt.transparent) painter = QPainter(original) painter.setRenderHint(QPainter.Antialiasing) painter.setPen(Qt.NoPen) painter.setBrush(Qt.white) points = [ QPointF(1, 1), QPointF(arrow_width - 1, 1), QPointF(arrow_width / 2, arrow_height - 1) ] painter.drawPolygon(QPolygonF(points)) painter.end() down_pixmap = original.scaled(arrow_width, arrow_height, Qt.KeepAspectRatio, Qt.SmoothTransformation) # 垂直翻转得到向上箭头 transform = QTransform() transform.scale(1, -1) up_pixmap = down_pixmap.transformed(transform) # 保存到临时文件 temp_dir = tempfile.gettempdir() cls._arrow_down_path = os.path.join(temp_dir, "arrow_down.png").replace("\\", "/") cls._arrow_up_path = os.path.join(temp_dir, "arrow_up.png").replace("\\", "/") down_pixmap.save(cls._arrow_down_path, "PNG") up_pixmap.save(cls._arrow_up_path, "PNG") cls._initialized = True @classmethod def get_arrow_paths(cls): """获取箭头图片路径 Returns: tuple: (向下箭头路径, 向上箭头路径) """ return cls._arrow_down_path, cls._arrow_up_path def __init__(self, parent=None): super().__init__(parent) ArrowComboBox.init_arrow_resources() self._is_open = False self._updating_style = False self._arrow_size = (14, 8) # 默认箭头尺寸 # 延迟初始化箭头(等待样式设置后) QTimer.singleShot(0, self._update_arrow_style) @property def isPopupOpen(self) -> bool: """返回下拉列表是否打开""" return self._is_open @property def arrowDirection(self) -> str: """返回当前箭头方向 ('up' 或 'down')""" return 'up' if self._is_open else 'down' def setArrowSize(self, width: int, height: int): """设置箭头尺寸 Args: width: 箭头宽度 height: 箭头高度 """ self._arrow_size = (width, height) self._update_arrow_style() def setArrowDirection(self, is_up: bool): """手动设置箭头方向 Args: is_up: True 为向上,False 为向下 """ if self._is_open != is_up: self._is_open = is_up self._update_arrow_style() self.arrowDirectionChanged.emit(is_up) def showPopup(self): """展开下拉列表时切换为向上箭头""" super().showPopup() self._is_open = True self._update_arrow_style() self.popupShown.emit() self.arrowDirectionChanged.emit(True) def hidePopup(self): """收起下拉列表时切换为向下箭头""" super().hidePopup() self._is_open = False self._update_arrow_style() self.popupHidden.emit() self.arrowDirectionChanged.emit(False) def togglePopup(self): """切换下拉列表的展开/收起状态""" if self._is_open: self.hidePopup() else: self.showPopup() def refreshArrowStyle(self): """刷新箭头样式(外部调用)""" self._update_arrow_style() def _update_arrow_style(self): """更新箭头样式(内部方法)""" if self._updating_style: return self._updating_style = True try: arrow_path = self._arrow_up_path if self._is_open else self._arrow_down_path arrow_width, arrow_height = self._arrow_size style = self.styleSheet() # 移除旧的箭头样式 style = re.sub(r'QComboBox::down-arrow\s*\{[^}]*\}', '', style) style += f"""QComboBox::down-arrow {{ image: url({arrow_path}); width: {arrow_width}px; height: {arrow_height}px; }}""" super().setStyleSheet(style) finally: self._updating_style = False