You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
316 lines
9.9 KiB
316 lines
9.9 KiB
# coding:utf-8
|
|
from enum import Enum
|
|
|
|
from PySide6.QtCore import QEvent, QObject, QPoint, QTimer, Qt, QPropertyAnimation, QSize
|
|
from PySide6.QtGui import QColor, QCursor
|
|
from PySide6.QtWidgets import (QApplication, QFrame, QGraphicsDropShadowEffect,
|
|
QHBoxLayout, QLabel, QWidget)
|
|
|
|
from ...common import FluentStyleSheet
|
|
|
|
|
|
class ToolTipPosition(Enum):
|
|
""" Info bar position """
|
|
|
|
TOP = 0
|
|
BOTTOM = 1
|
|
LEFT = 2
|
|
RIGHT = 3
|
|
TOP_LEFT = 4
|
|
TOP_RIGHT = 5
|
|
BOTTOM_LEFT = 6
|
|
BOTTOM_RIGHT = 7
|
|
|
|
|
|
class ToolTip(QFrame):
|
|
""" Tool tip """
|
|
|
|
def __init__(self, text='', parent=None):
|
|
"""
|
|
Parameters
|
|
----------
|
|
text: str
|
|
the text of tool tip
|
|
|
|
parent: QWidget
|
|
parent widget
|
|
"""
|
|
super().__init__(parent=parent)
|
|
self.__text = text
|
|
self.__duration = 1000
|
|
|
|
self.container = QFrame(self)
|
|
self.timer = QTimer(self)
|
|
|
|
self.setLayout(QHBoxLayout())
|
|
self.containerLayout = QHBoxLayout(self.container)
|
|
self.label = QLabel(text, self)
|
|
|
|
# set layout
|
|
self.layout().setContentsMargins(12, 8, 12, 12)
|
|
self.layout().addWidget(self.container)
|
|
self.containerLayout.addWidget(self.label)
|
|
self.containerLayout.setContentsMargins(8, 6, 8, 6)
|
|
|
|
# add opacity effect
|
|
self.opacityAni = QPropertyAnimation(self, b'windowOpacity', self)
|
|
self.opacityAni.setDuration(150)
|
|
|
|
# add shadow
|
|
self.shadowEffect = QGraphicsDropShadowEffect(self)
|
|
self.shadowEffect.setBlurRadius(25)
|
|
self.shadowEffect.setColor(QColor(0, 0, 0, 60))
|
|
self.shadowEffect.setOffset(0, 5)
|
|
self.container.setGraphicsEffect(self.shadowEffect)
|
|
|
|
self.timer.setSingleShot(True)
|
|
self.timer.timeout.connect(self.hide)
|
|
|
|
# set style
|
|
self.setAttribute(Qt.WA_TransparentForMouseEvents)
|
|
self.setAttribute(Qt.WA_TranslucentBackground)
|
|
self.setWindowFlags(Qt.Tool | Qt.FramelessWindowHint)
|
|
self.__setQss()
|
|
|
|
def text(self):
|
|
return self.__text
|
|
|
|
def setText(self, text):
|
|
""" set text on tooltip """
|
|
self.__text = text
|
|
self.label.setText(text)
|
|
self.container.adjustSize()
|
|
self.adjustSize()
|
|
|
|
def duration(self):
|
|
return self.__duration
|
|
|
|
def setDuration(self, duration: int):
|
|
""" set tooltip duration in milliseconds
|
|
|
|
Parameters
|
|
----------
|
|
duration: int
|
|
display duration in milliseconds, if `duration <= 0`, tooltip won't disappear automatically
|
|
"""
|
|
self.__duration = duration
|
|
|
|
def __setQss(self):
|
|
""" set style sheet """
|
|
self.container.setObjectName("container")
|
|
self.label.setObjectName("contentLabel")
|
|
FluentStyleSheet.TOOL_TIP.apply(self)
|
|
self.label.adjustSize()
|
|
self.adjustSize()
|
|
|
|
def showEvent(self, e):
|
|
self.opacityAni.setStartValue(0)
|
|
self.opacityAni.setEndValue(1)
|
|
self.opacityAni.start()
|
|
|
|
self.timer.stop()
|
|
if self.duration() > 0:
|
|
self.timer.start(self.__duration + self.opacityAni.duration())
|
|
|
|
super().showEvent(e)
|
|
|
|
def hideEvent(self, e):
|
|
self.timer.stop()
|
|
super().hideEvent(e)
|
|
|
|
def adjustPos(self, widget, position: ToolTipPosition):
|
|
""" adjust the position of tooltip relative to widget """
|
|
manager = ToolTipPositionManager.make(position)
|
|
self.move(manager.position(self, widget))
|
|
|
|
|
|
class ToolTipPositionManager:
|
|
""" Tooltip position manager """
|
|
|
|
def position(self, tooltip: ToolTip, parent: QWidget) -> QPoint:
|
|
pos = self._pos(tooltip, parent)
|
|
x, y = pos.x(), pos.y()
|
|
|
|
rect = QApplication.screenAt(QCursor.pos()).availableGeometry()
|
|
x = min(max(-2, x) if QCursor().pos().x() >= 0 else x, rect.width() - tooltip.width() - 4)
|
|
y = min(max(-2, y), rect.height() - tooltip.height() - 4)
|
|
|
|
return QPoint(x, y)
|
|
|
|
def _pos(self, tooltip: ToolTip, parent: QWidget) -> QPoint:
|
|
raise NotImplementedError
|
|
|
|
@staticmethod
|
|
def make(position: ToolTipPosition):
|
|
""" mask info bar manager according to the display position """
|
|
managers = {
|
|
ToolTipPosition.TOP: TopToolTipManager,
|
|
ToolTipPosition.BOTTOM: BottomToolTipManager,
|
|
ToolTipPosition.LEFT: LeftToolTipManager,
|
|
ToolTipPosition.RIGHT: RightToolTipManager,
|
|
ToolTipPosition.TOP_RIGHT: TopRightToolTipManager,
|
|
ToolTipPosition.BOTTOM_RIGHT: BottomRightToolTipManager,
|
|
ToolTipPosition.TOP_LEFT: TopLeftToolTipManager,
|
|
ToolTipPosition.BOTTOM_LEFT: BottomLeftToolTipManager,
|
|
}
|
|
|
|
if position not in managers:
|
|
raise ValueError(f'`{position}` is an invalid info bar position.')
|
|
|
|
return managers[position]()
|
|
|
|
|
|
class TopToolTipManager(ToolTipPositionManager):
|
|
""" Top tooltip position manager """
|
|
|
|
def _pos(self, tooltip: ToolTip, parent: QWidget):
|
|
pos = parent.mapToGlobal(QPoint())
|
|
x = pos.x() + parent.width()//2 - tooltip.width()//2
|
|
y = pos.y() - tooltip.height()
|
|
return QPoint(x, y)
|
|
|
|
|
|
class BottomToolTipManager(ToolTipPositionManager):
|
|
""" Bottom tooltip position manager """
|
|
|
|
def _pos(self, tooltip: ToolTip, parent: QWidget) -> QPoint:
|
|
pos = parent.mapToGlobal(QPoint())
|
|
x = pos.x() + parent.width()//2 - tooltip.width()//2
|
|
y = pos.y() + parent.height()
|
|
return QPoint(x, y)
|
|
|
|
|
|
class LeftToolTipManager(ToolTipPositionManager):
|
|
""" Left tooltip position manager """
|
|
|
|
def _pos(self, tooltip: ToolTip, parent: QWidget) -> QPoint:
|
|
pos = parent.mapToGlobal(QPoint())
|
|
x = pos.x() - tooltip.width()
|
|
y = pos.y() + (parent.height() - tooltip.height()) // 2
|
|
return QPoint(x, y)
|
|
|
|
|
|
class RightToolTipManager(ToolTipPositionManager):
|
|
""" Right tooltip position manager """
|
|
|
|
def _pos(self, tooltip: ToolTip, parent: QWidget) -> QPoint:
|
|
pos = parent.mapToGlobal(QPoint())
|
|
x = pos.x() + parent.width()
|
|
y = pos.y() + (parent.height() - tooltip.height()) // 2
|
|
return QPoint(x, y)
|
|
|
|
|
|
class TopRightToolTipManager(ToolTipPositionManager):
|
|
""" Top right tooltip position manager """
|
|
|
|
def _pos(self, tooltip: ToolTip, parent: QWidget) -> QPoint:
|
|
pos = parent.mapToGlobal(QPoint())
|
|
x = pos.x() + parent.width() - tooltip.width() + \
|
|
tooltip.layout().contentsMargins().right()
|
|
y = pos.y() - tooltip.height()
|
|
return QPoint(x, y)
|
|
|
|
|
|
class TopLeftToolTipManager(ToolTipPositionManager):
|
|
""" Top left tooltip position manager """
|
|
|
|
def _pos(self, tooltip: ToolTip, parent: QWidget) -> QPoint:
|
|
pos = parent.mapToGlobal(QPoint())
|
|
x = pos.x() - tooltip.layout().contentsMargins().left()
|
|
y = pos.y() - tooltip.height()
|
|
return QPoint(x, y)
|
|
|
|
|
|
class BottomRightToolTipManager(ToolTipPositionManager):
|
|
""" Bottom right tooltip position manager """
|
|
|
|
def _pos(self, tooltip: ToolTip, parent: QWidget) -> QPoint:
|
|
pos = parent.mapToGlobal(QPoint())
|
|
x = pos.x() + parent.width() - tooltip.width() + \
|
|
tooltip.layout().contentsMargins().right()
|
|
y = pos.y() + parent.height()
|
|
return QPoint(x, y)
|
|
|
|
|
|
class BottomLeftToolTipManager(ToolTipPositionManager):
|
|
""" Bottom left tooltip position manager """
|
|
|
|
def _pos(self, tooltip: ToolTip, parent: QWidget) -> QPoint:
|
|
pos = parent.mapToGlobal(QPoint())
|
|
x = pos.x() - tooltip.layout().contentsMargins().left()
|
|
y = pos.y() + parent.height()
|
|
return QPoint(x, y)
|
|
|
|
|
|
class ToolTipFilter(QObject):
|
|
""" Tool button with a tool tip """
|
|
|
|
def __init__(self, parent: QWidget, showDelay=300, position=ToolTipPosition.TOP):
|
|
"""
|
|
Parameters
|
|
----------
|
|
parent: QWidget
|
|
the widget to install tool tip
|
|
|
|
showDelay: int
|
|
show tool tip after how long the mouse hovers in milliseconds
|
|
|
|
position: TooltipPosition
|
|
where to show the tooltip
|
|
"""
|
|
super().__init__(parent=parent)
|
|
self.isEnter = False
|
|
self._tooltip = None
|
|
self._tooltipDelay = showDelay
|
|
self.position = position
|
|
self.timer = QTimer(self)
|
|
self.timer.setSingleShot(True)
|
|
self.timer.timeout.connect(self.showToolTip)
|
|
|
|
def eventFilter(self, obj: QObject, e: QEvent) -> bool:
|
|
if e.type() == QEvent.ToolTip:
|
|
return True
|
|
elif e.type() in [QEvent.Hide, QEvent.Leave]:
|
|
self.hideToolTip()
|
|
elif e.type() == QEvent.Enter:
|
|
self.isEnter = True
|
|
parent = self.parent() # type: QWidget
|
|
if self._canShowToolTip():
|
|
if self._tooltip is None:
|
|
self._tooltip = ToolTip(parent.toolTip(), parent.window())
|
|
|
|
t = parent.toolTipDuration() if parent.toolTipDuration() > 0 else -1
|
|
self._tooltip.setDuration(t)
|
|
|
|
# show the tool tip after delay
|
|
self.timer.start(self._tooltipDelay)
|
|
elif e.type() == QEvent.MouseButtonPress:
|
|
self.hideToolTip()
|
|
|
|
return super().eventFilter(obj, e)
|
|
|
|
def hideToolTip(self):
|
|
""" hide tool tip """
|
|
self.isEnter = False
|
|
self.timer.stop()
|
|
if self._tooltip:
|
|
self._tooltip.hide()
|
|
|
|
def showToolTip(self):
|
|
""" show tool tip """
|
|
if not self.isEnter:
|
|
return
|
|
|
|
parent = self.parent() # type: QWidget
|
|
self._tooltip.setText(parent.toolTip())
|
|
self._tooltip.adjustPos(parent, self.position)
|
|
self._tooltip.show()
|
|
|
|
def setToolTipDelay(self, delay: int):
|
|
""" set the delay of tool tip """
|
|
self._tooltipDelay = delay
|
|
|
|
def _canShowToolTip(self) -> bool:
|
|
parent = self.parent() # type: QWidget
|
|
return parent.isWidgetType() and parent.toolTip() and parent.isEnabled()
|