177 lines
6.0 KiB
Python
177 lines
6.0 KiB
Python
|
|
# -*- 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
|