handOver2/ui/components/popup_widget.py

300 lines
8.7 KiB
Python

# -*- coding: utf-8 -*-
"""
팝업 위젯 모듈
마우스 호버 시 표시되는 팝업 위젯을 정의합니다.
"""
from PySide6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QGraphicsDropShadowEffect
)
from PySide6.QtCore import Qt, Signal, QTimer, QPoint
from PySide6.QtGui import QFont, QColor
from core.config import ConfigManager
from core.logger import get_logger
logger = get_logger(__name__)
class PopupWidget(QWidget):
"""
팝업 위젯
마우스 호버 시 표시되는 팝업 정보 창입니다.
일정 시간 후 자동으로 사라집니다.
Signals:
action_clicked: 액션 버튼 클릭 시그널
popup_closed: 팝업 닫힘 시그널
Examples:
>>> popup = PopupWidget(title="정보")
>>> popup.add_row("항목1", "값1")
>>> popup.show_at(QPoint(100, 100))
"""
action_clicked = Signal(str) # 액션 ID
popup_closed = Signal()
def __init__(
self,
parent=None,
title: str = "",
width: int = 280,
auto_hide: bool = True,
auto_hide_delay: int = 3000
):
super().__init__(parent)
self.config = ConfigManager()
self.title_text = title
self._width = width
self._auto_hide = auto_hide
self._auto_hide_delay = auto_hide_delay
# 숨김 타이머
self._hide_timer = QTimer()
self._hide_timer.setSingleShot(True)
self._hide_timer.timeout.connect(self.hide_popup)
# 윈도우 설정
self.setWindowFlags(
Qt.ToolTip |
Qt.FramelessWindowHint |
Qt.WindowStaysOnTopHint
)
self.setAttribute(Qt.WA_TranslucentBackground)
self._setup_ui()
def _setup_ui(self):
"""UI 설정"""
# 메인 레이아웃
main_layout = QVBoxLayout(self)
main_layout.setContentsMargins(0, 0, 0, 0)
# 컨테이너
self.container = QWidget()
self.container.setObjectName("popupContainer")
self.container.setFixedWidth(self._width)
main_layout.addWidget(self.container)
# 컨테이너 레이아웃
self.container_layout = QVBoxLayout(self.container)
self.container_layout.setContentsMargins(16, 12, 16, 12)
self.container_layout.setSpacing(8)
# 헤더
if self.title_text:
self.title_label = QLabel(self.title_text)
self.title_label.setObjectName("popupTitle")
self.title_label.setFont(QFont("GmarketSans", 13, QFont.Bold))
self.container_layout.addWidget(self.title_label)
# 컨텐츠 영역
self.content_widget = QWidget()
self.content_layout = QVBoxLayout(self.content_widget)
self.content_layout.setContentsMargins(0, 0, 0, 0)
self.content_layout.setSpacing(6)
self.container_layout.addWidget(self.content_widget)
# 액션 영역
self.action_widget = QWidget()
self.action_layout = QHBoxLayout(self.action_widget)
self.action_layout.setContentsMargins(0, 8, 0, 0)
self.action_layout.setSpacing(8)
self.action_widget.hide()
self.container_layout.addWidget(self.action_widget)
# 그림자 효과
shadow = QGraphicsDropShadowEffect()
shadow.setBlurRadius(20)
shadow.setOffset(0, 4)
shadow.setColor(QColor(0, 0, 0, 60))
self.container.setGraphicsEffect(shadow)
# 스타일 적용
self._apply_style()
def _apply_style(self):
"""스타일 적용"""
theme = self.config.theme
if theme == 'dark':
bg = "#1e293b"
border = "#334155"
text = "#f8fafc"
secondary = "#94a3b8"
else:
bg = "#ffffff"
border = "#e2e8f0"
text = "#1e293b"
secondary = "#64748b"
self.setStyleSheet(f"""
#popupContainer {{
background-color: {bg};
border: 1px solid {border};
border-radius: 12px;
}}
#popupTitle {{
color: {text};
font-weight: bold;
}}
QLabel {{
color: {text};
}}
.secondary {{
color: {secondary};
}}
""")
def add_row(self, label: str, value: str, is_secondary: bool = False):
"""
행 추가
Args:
label: 라벨
value: 값
is_secondary: 보조 텍스트 여부
"""
row = QWidget()
row_layout = QHBoxLayout(row)
row_layout.setContentsMargins(0, 0, 0, 0)
row_layout.setSpacing(8)
label_widget = QLabel(label)
label_widget.setFont(QFont("GmarketSans", 12))
if is_secondary:
label_widget.setProperty("class", "secondary")
value_widget = QLabel(value)
value_widget.setFont(QFont("GmarketSans", 12))
value_widget.setAlignment(Qt.AlignRight)
row_layout.addWidget(label_widget)
row_layout.addStretch()
row_layout.addWidget(value_widget)
self.content_layout.addWidget(row)
def add_text(self, text: str, is_secondary: bool = False):
"""
텍스트 추가
Args:
text: 텍스트
is_secondary: 보조 텍스트 여부
"""
label = QLabel(text)
label.setFont(QFont("GmarketSans", 12))
label.setWordWrap(True)
if is_secondary:
label.setProperty("class", "secondary")
self.content_layout.addWidget(label)
def add_action(self, text: str, action_id: str, primary: bool = False):
"""
액션 버튼 추가
Args:
text: 버튼 텍스트
action_id: 액션 ID
primary: 주요 버튼 여부
"""
self.action_widget.show()
btn = QPushButton(text)
btn.setCursor(Qt.PointingHandCursor)
btn.setFont(QFont("GmarketSans", 11))
btn.clicked.connect(lambda: self._on_action_clicked(action_id))
if primary:
btn.setStyleSheet("""
QPushButton {
background-color: #3b82f6;
color: white;
border: none;
border-radius: 6px;
padding: 6px 12px;
}
QPushButton:hover {
background-color: #2563eb;
}
""")
else:
btn.setStyleSheet("""
QPushButton {
background-color: transparent;
color: #3b82f6;
border: none;
padding: 6px 12px;
}
QPushButton:hover {
background-color: #f1f5f9;
border-radius: 6px;
}
""")
self.action_layout.addWidget(btn)
def _on_action_clicked(self, action_id: str):
"""액션 버튼 클릭"""
self.action_clicked.emit(action_id)
self.hide_popup()
def show_at(self, position: QPoint):
"""
지정된 위치에 팝업 표시
Args:
position: 표시 위치
"""
self.adjustSize()
self.move(position)
self.show()
if self._auto_hide:
self._hide_timer.start(self._auto_hide_delay)
def hide_popup(self):
"""팝업 숨기기"""
self._hide_timer.stop()
self.hide()
self.popup_closed.emit()
def clear(self):
"""컨텐츠 초기화"""
# 컨텐츠 위젯 비우기
while self.content_layout.count():
item = self.content_layout.takeAt(0)
if item.widget():
item.widget().deleteLater()
# 액션 위젯 비우기
while self.action_layout.count():
item = self.action_layout.takeAt(0)
if item.widget():
item.widget().deleteLater()
self.action_widget.hide()
def enterEvent(self, event):
"""마우스 진입 - 자동 숨김 중지"""
self._hide_timer.stop()
super().enterEvent(event)
def leaveEvent(self, event):
"""마우스 이탈 - 자동 숨김 재시작"""
if self._auto_hide:
self._hide_timer.start(self._auto_hide_delay)
super().leaveEvent(event)