300 lines
8.7 KiB
Python
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)
|
|
|
|
|