781 lines
23 KiB
Python
781 lines
23 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
커스텀 입력 필드 모듈
|
|
다양한 스타일의 커스텀 입력 필드를 정의합니다.
|
|
|
|
이 모듈은 다음 입력 필드를 제공합니다:
|
|
- CustomLineEdit: 한 줄 입력 필드
|
|
- CustomTextEdit: 여러 줄 입력 필드
|
|
- CustomComboBox: 드롭다운 선택 필드
|
|
- CustomDateInput: 날짜 입력 필드 (달력 팝업)
|
|
- CustomDateRangeInput: 기간 입력 필드 (달력 팝업)
|
|
"""
|
|
|
|
from datetime import date
|
|
from typing import List, Optional, Tuple
|
|
|
|
from PySide6.QtWidgets import (
|
|
QLineEdit, QTextEdit, QComboBox, QWidget, QVBoxLayout,
|
|
QHBoxLayout, QLabel, QCompleter, QPushButton, QFrame
|
|
)
|
|
from PySide6.QtCore import Qt, Signal, QStringListModel, QPoint
|
|
from PySide6.QtGui import QFont, QColor
|
|
|
|
from core.config import ConfigManager
|
|
from core.logger import get_logger
|
|
from ui.styles.style_manager import StyleManager
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
class CustomLineEdit(QLineEdit):
|
|
"""
|
|
커스텀 한 줄 입력 필드
|
|
|
|
현대적인 디자인의 입력 필드입니다.
|
|
플레이스홀더, 아이콘, 유효성 검사 기능을 제공합니다.
|
|
|
|
Attributes:
|
|
label: 입력 필드 라벨
|
|
required: 필수 입력 여부
|
|
|
|
Examples:
|
|
>>> input_field = CustomLineEdit(
|
|
... placeholder="이름을 입력하세요",
|
|
... label="이름",
|
|
... required=True
|
|
... )
|
|
"""
|
|
|
|
# 시그널
|
|
validation_changed = Signal(bool) # 유효성 변경 시그널
|
|
|
|
def __init__(
|
|
self,
|
|
parent=None,
|
|
placeholder: str = "",
|
|
label: str = "",
|
|
required: bool = False,
|
|
prefix_icon: str = "",
|
|
suffix_icon: str = "",
|
|
max_length: int = None,
|
|
completions: List[str] = None
|
|
):
|
|
super().__init__(parent)
|
|
|
|
self.config = ConfigManager()
|
|
self.style_manager = StyleManager()
|
|
self._label = label
|
|
self._required = required
|
|
self._is_valid = True
|
|
|
|
# 기본 설정
|
|
self.setPlaceholderText(placeholder)
|
|
|
|
# 폰트 설정 (스타일 관리자 사용)
|
|
input_font = self.style_manager.get_font("dialog", "input")
|
|
self.setFont(input_font)
|
|
|
|
if max_length:
|
|
self.setMaxLength(max_length)
|
|
|
|
# 자동 완성
|
|
if completions:
|
|
completer = QCompleter(completions)
|
|
completer.setCaseSensitivity(Qt.CaseInsensitive)
|
|
self.setCompleter(completer)
|
|
|
|
# 유효성 검사 연결
|
|
self.textChanged.connect(self._validate)
|
|
|
|
self._apply_style()
|
|
|
|
def _apply_style(self):
|
|
"""스타일 적용"""
|
|
colors = self.style_manager.get_colors()
|
|
input_font = self.style_manager.get_font("dialog", "input")
|
|
|
|
# 높이 자동 계산
|
|
height = self.style_manager.calculate_input_height(
|
|
font=input_font, area="dialog", style="input"
|
|
)
|
|
|
|
border_color = colors['error'] if not self._is_valid else colors['input_border']
|
|
|
|
self.setStyleSheet(f"""
|
|
QLineEdit {{
|
|
background-color: {colors['input_bg']};
|
|
color: {colors['input_text']};
|
|
border: 1px solid {border_color};
|
|
border-radius: 6px;
|
|
padding: {height // 4}px 12px;
|
|
font-family: '{input_font.family()}';
|
|
font-size: {input_font.pointSize()}pt;
|
|
min-height: {height}px;
|
|
}}
|
|
|
|
QLineEdit:focus {{
|
|
border-color: {colors['input_focus']};
|
|
outline: none;
|
|
}}
|
|
|
|
QLineEdit:disabled {{
|
|
background-color: {colors['bg_tertiary']};
|
|
color: {colors['text_disabled']};
|
|
}}
|
|
|
|
QLineEdit::placeholder {{
|
|
color: {colors['text_tertiary']};
|
|
}}
|
|
""")
|
|
|
|
def _validate(self):
|
|
"""유효성 검사"""
|
|
if self._required and not self.text().strip():
|
|
self._is_valid = False
|
|
else:
|
|
self._is_valid = True
|
|
|
|
self._apply_style()
|
|
self.validation_changed.emit(self._is_valid)
|
|
|
|
@property
|
|
def is_valid(self) -> bool:
|
|
"""유효성 상태 반환"""
|
|
return self._is_valid
|
|
|
|
def set_error(self, is_error: bool):
|
|
"""에러 상태 설정"""
|
|
self._is_valid = not is_error
|
|
self._apply_style()
|
|
|
|
|
|
class CustomTextEdit(QTextEdit):
|
|
"""
|
|
커스텀 여러 줄 입력 필드
|
|
|
|
여러 줄의 텍스트를 입력할 수 있는 필드입니다.
|
|
|
|
Examples:
|
|
>>> text_edit = CustomTextEdit(placeholder="내용을 입력하세요")
|
|
"""
|
|
|
|
# 시그널
|
|
text_changed_signal = Signal(str)
|
|
|
|
def __init__(
|
|
self,
|
|
parent=None,
|
|
placeholder: str = "",
|
|
min_height: int = 100,
|
|
max_height: int = None,
|
|
read_only: bool = False
|
|
):
|
|
super().__init__(parent)
|
|
|
|
self.config = ConfigManager()
|
|
|
|
# 기본 설정
|
|
self.setPlaceholderText(placeholder)
|
|
self.setFont(QFont("GmarketSans", 13))
|
|
self.setMinimumHeight(min_height)
|
|
self.setReadOnly(read_only)
|
|
|
|
if max_height:
|
|
self.setMaximumHeight(max_height)
|
|
|
|
# 시그널 연결
|
|
self.textChanged.connect(lambda: self.text_changed_signal.emit(self.toPlainText()))
|
|
|
|
self._apply_style()
|
|
|
|
def _apply_style(self):
|
|
"""스타일 적용"""
|
|
theme = self.config.theme
|
|
|
|
if theme == 'dark':
|
|
bg = "#1e293b"
|
|
border = "#334155"
|
|
text = "#f8fafc"
|
|
placeholder = "#64748b"
|
|
focus_border = "#3b82f6"
|
|
scrollbar_bg = "#1e293b"
|
|
scrollbar_handle = "#475569"
|
|
else:
|
|
bg = "#ffffff"
|
|
border = "#e2e8f0"
|
|
text = "#1e293b"
|
|
placeholder = "#94a3b8"
|
|
focus_border = "#3b82f6"
|
|
scrollbar_bg = "#f8fafc"
|
|
scrollbar_handle = "#cbd5e1"
|
|
|
|
self.setStyleSheet(f"""
|
|
QTextEdit {{
|
|
background-color: {bg};
|
|
color: {text};
|
|
border: 2px solid {border};
|
|
border-radius: 8px;
|
|
padding: 10px;
|
|
font-size: 14px;
|
|
}}
|
|
|
|
QTextEdit:focus {{
|
|
border-color: {focus_border};
|
|
}}
|
|
|
|
QScrollBar:vertical {{
|
|
background-color: {scrollbar_bg};
|
|
width: 10px;
|
|
border-radius: 5px;
|
|
}}
|
|
|
|
QScrollBar::handle:vertical {{
|
|
background-color: {scrollbar_handle};
|
|
border-radius: 5px;
|
|
min-height: 20px;
|
|
}}
|
|
|
|
QScrollBar::handle:vertical:hover {{
|
|
background-color: {focus_border};
|
|
}}
|
|
""")
|
|
|
|
def set_text(self, text: str):
|
|
"""텍스트 설정"""
|
|
self.setPlainText(text)
|
|
|
|
def get_text(self) -> str:
|
|
"""텍스트 반환"""
|
|
return self.toPlainText()
|
|
|
|
|
|
class CustomComboBox(QComboBox):
|
|
"""
|
|
커스텀 드롭다운 선택 필드
|
|
|
|
목록에서 항목을 선택할 수 있는 드롭다운입니다.
|
|
|
|
Examples:
|
|
>>> combo = CustomComboBox(
|
|
... items=["옵션1", "옵션2", "옵션3"],
|
|
... placeholder="선택하세요"
|
|
... )
|
|
"""
|
|
|
|
# 시그널
|
|
selection_changed = Signal(str)
|
|
|
|
def __init__(
|
|
self,
|
|
parent=None,
|
|
items: List[str] = None,
|
|
placeholder: str = "",
|
|
editable: bool = False,
|
|
current_index: int = -1
|
|
):
|
|
super().__init__(parent)
|
|
|
|
self.config = ConfigManager()
|
|
self.style_manager = StyleManager()
|
|
|
|
# 기본 설정
|
|
input_font = self.style_manager.get_font("dialog", "input")
|
|
self.setFont(input_font)
|
|
self.setEditable(editable)
|
|
|
|
# 플레이스홀더
|
|
if placeholder:
|
|
self.addItem(placeholder)
|
|
self.setCurrentIndex(0)
|
|
self.model().item(0).setEnabled(False)
|
|
|
|
# 아이템 추가
|
|
if items:
|
|
self.addItems(items)
|
|
|
|
if current_index >= 0:
|
|
actual_index = current_index + (1 if placeholder else 0)
|
|
self.setCurrentIndex(actual_index)
|
|
|
|
# 시그널 연결
|
|
self.currentTextChanged.connect(self.selection_changed.emit)
|
|
|
|
self._apply_style()
|
|
|
|
def _apply_style(self):
|
|
"""스타일 적용"""
|
|
colors = self.style_manager.get_colors()
|
|
input_font = self.style_manager.get_font("dialog", "input")
|
|
|
|
# 높이 자동 계산
|
|
height = self.style_manager.calculate_input_height(
|
|
font=input_font, area="dialog", style="input"
|
|
)
|
|
|
|
self.setStyleSheet(f"""
|
|
QComboBox {{
|
|
background-color: {colors['input_bg']};
|
|
color: {colors['input_text']};
|
|
border: 1px solid {colors['input_border']};
|
|
border-radius: 6px;
|
|
padding: {height // 4}px 12px;
|
|
font-family: '{input_font.family()}';
|
|
font-size: {input_font.pointSize()}pt;
|
|
min-height: {height}px;
|
|
}}
|
|
|
|
QComboBox:hover {{
|
|
border-color: {colors['input_focus']};
|
|
}}
|
|
|
|
QComboBox:focus {{
|
|
border-color: {colors['input_focus']};
|
|
outline: none;
|
|
}}
|
|
|
|
QComboBox::drop-down {{
|
|
border: none;
|
|
width: 25px;
|
|
}}
|
|
|
|
QComboBox::down-arrow {{
|
|
image: none;
|
|
border-left: 5px solid transparent;
|
|
border-right: 5px solid transparent;
|
|
border-top: 6px solid {colors['input_text']};
|
|
margin-right: 5px;
|
|
}}
|
|
|
|
QComboBox QAbstractItemView {{
|
|
background-color: {colors['input_bg']};
|
|
color: {colors['input_text']};
|
|
border: 1px solid {colors['input_border']};
|
|
border-radius: 8px;
|
|
padding: 4px;
|
|
selection-background-color: {colors['accent']};
|
|
selection-color: {colors['btn_primary_text']};
|
|
}}
|
|
|
|
QComboBox QAbstractItemView::item {{
|
|
padding: 8px 12px;
|
|
border-radius: 4px;
|
|
}}
|
|
|
|
QComboBox QAbstractItemView::item:hover {{
|
|
background-color: {colors['bg_hover']};
|
|
}}
|
|
""")
|
|
|
|
def get_selected_value(self) -> Optional[str]:
|
|
"""선택된 값 반환"""
|
|
text = self.currentText()
|
|
# 플레이스홀더 체크
|
|
if self.currentIndex() == 0 and not self.itemData(0):
|
|
return None
|
|
return text
|
|
|
|
def set_selected_value(self, value: str):
|
|
"""값으로 선택"""
|
|
index = self.findText(value)
|
|
if index >= 0:
|
|
self.setCurrentIndex(index)
|
|
|
|
def clear_selection(self):
|
|
"""선택 초기화"""
|
|
if self.count() > 0:
|
|
self.setCurrentIndex(0)
|
|
|
|
|
|
class LabeledInput(QWidget):
|
|
"""
|
|
라벨이 있는 입력 필드 컨테이너
|
|
|
|
라벨과 입력 필드를 함께 제공합니다.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
label: str,
|
|
input_widget: QWidget,
|
|
parent=None,
|
|
required: bool = False,
|
|
horizontal: bool = False
|
|
):
|
|
super().__init__(parent)
|
|
|
|
self.config = ConfigManager()
|
|
self.style_manager = StyleManager()
|
|
self.input_widget = input_widget
|
|
|
|
self._setup_ui(label, required, horizontal)
|
|
|
|
def _setup_ui(self, label: str, required: bool, horizontal: bool):
|
|
"""UI 설정"""
|
|
if horizontal:
|
|
layout = QHBoxLayout(self)
|
|
else:
|
|
layout = QVBoxLayout(self)
|
|
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
layout.setSpacing(6)
|
|
|
|
# 라벨
|
|
label_text = f"{label} *" if required else label
|
|
self.label = QLabel(label_text)
|
|
|
|
# 폰트 설정 (스타일 관리자 사용)
|
|
label_font = self.style_manager.get_font("dialog", "label")
|
|
self.label.setFont(label_font)
|
|
|
|
# 높이 계산
|
|
label_height = self.style_manager.calculate_label_height(
|
|
font=label_font, area="dialog", style="label"
|
|
)
|
|
|
|
# 색상 설정
|
|
colors = self.style_manager.get_colors()
|
|
text_color = colors['text_primary']
|
|
required_color = colors['error']
|
|
|
|
self.label.setStyleSheet(f"""
|
|
QLabel {{
|
|
color: {text_color};
|
|
font-family: '{label_font.family()}';
|
|
font-size: {label_font.pointSize()}pt;
|
|
min-height: {label_height}px;
|
|
}}
|
|
""")
|
|
|
|
layout.addWidget(self.label)
|
|
layout.addWidget(self.input_widget, 1 if horizontal else 0)
|
|
|
|
def get_value(self):
|
|
"""입력 값 반환"""
|
|
if isinstance(self.input_widget, QLineEdit):
|
|
return self.input_widget.text()
|
|
elif isinstance(self.input_widget, QTextEdit):
|
|
return self.input_widget.toPlainText()
|
|
elif isinstance(self.input_widget, QComboBox):
|
|
return self.input_widget.currentText()
|
|
return None
|
|
|
|
def set_value(self, value):
|
|
"""입력 값 설정"""
|
|
if isinstance(self.input_widget, QLineEdit):
|
|
self.input_widget.setText(str(value) if value else "")
|
|
elif isinstance(self.input_widget, QTextEdit):
|
|
self.input_widget.setPlainText(str(value) if value else "")
|
|
elif isinstance(self.input_widget, QComboBox):
|
|
self.input_widget.setCurrentText(str(value) if value else "")
|
|
|
|
|
|
class CalendarPopup(QFrame):
|
|
"""
|
|
달력 팝업 프레임
|
|
|
|
날짜 입력 필드에서 사용하는 팝업 달력입니다.
|
|
"""
|
|
|
|
date_selected = Signal(object) # date
|
|
range_selected = Signal(object, object) # start_date, end_date
|
|
closed = Signal()
|
|
|
|
def __init__(self, parent=None, range_mode: bool = False):
|
|
super().__init__(parent, Qt.Popup | Qt.FramelessWindowHint)
|
|
|
|
self.config = ConfigManager()
|
|
self._range_mode = range_mode
|
|
|
|
self._setup_ui()
|
|
self._apply_style()
|
|
|
|
def _setup_ui(self):
|
|
"""UI 설정"""
|
|
from ui.components.custom_calendar import CustomCalendar
|
|
|
|
layout = QVBoxLayout(self)
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
layout.setSpacing(0)
|
|
|
|
self.calendar = CustomCalendar(show_range_toggle=self._range_mode)
|
|
self.calendar.date_selected.connect(self._on_date_selected)
|
|
self.calendar.range_selected.connect(self._on_range_selected)
|
|
|
|
layout.addWidget(self.calendar)
|
|
|
|
def _apply_style(self):
|
|
"""스타일 적용"""
|
|
theme = self.config.theme
|
|
|
|
if theme == 'dark':
|
|
bg = "#1e293b"
|
|
border = "#334155"
|
|
else:
|
|
bg = "#ffffff"
|
|
border = "#e2e8f0"
|
|
|
|
self.setStyleSheet(f"""
|
|
CalendarPopup {{
|
|
background-color: {bg};
|
|
border: 1px solid {border};
|
|
border-radius: 12px;
|
|
}}
|
|
""")
|
|
|
|
def _on_date_selected(self, selected_date: date):
|
|
"""날짜 선택됨"""
|
|
self.date_selected.emit(selected_date)
|
|
if not self._range_mode:
|
|
self.close()
|
|
|
|
def _on_range_selected(self, start: date, end: date):
|
|
"""기간 선택됨"""
|
|
self.range_selected.emit(start, end)
|
|
self.close()
|
|
|
|
def set_date(self, d: date):
|
|
"""날짜 설정"""
|
|
self.calendar.set_date(d)
|
|
|
|
def set_range(self, start: date, end: date):
|
|
"""기간 설정"""
|
|
self.calendar.set_range(start, end)
|
|
|
|
def closeEvent(self, event):
|
|
"""닫기 이벤트"""
|
|
self.closed.emit()
|
|
super().closeEvent(event)
|
|
|
|
|
|
class CustomDateInput(QWidget):
|
|
"""
|
|
커스텀 날짜 입력 필드
|
|
|
|
클릭 시 달력 팝업이 표시됩니다.
|
|
|
|
Signals:
|
|
date_changed: 날짜 변경 시그널 (date)
|
|
|
|
Examples:
|
|
>>> date_input = CustomDateInput(placeholder="날짜 선택")
|
|
>>> date_input.date_changed.connect(self.on_date_changed)
|
|
"""
|
|
|
|
date_changed = Signal(object) # date
|
|
|
|
def __init__(
|
|
self,
|
|
parent=None,
|
|
placeholder: str = "날짜 선택",
|
|
initial_date: date = None
|
|
):
|
|
super().__init__(parent)
|
|
|
|
self.config = ConfigManager()
|
|
self._date = initial_date or date.today()
|
|
self._popup: Optional[CalendarPopup] = None
|
|
|
|
self._setup_ui(placeholder)
|
|
|
|
def _setup_ui(self, placeholder: str):
|
|
"""UI 설정"""
|
|
layout = QHBoxLayout(self)
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
layout.setSpacing(0)
|
|
|
|
theme = self.config.theme
|
|
is_dark = theme == 'dark'
|
|
|
|
# 날짜 표시 버튼 (클릭 가능)
|
|
self.date_btn = QPushButton()
|
|
self.date_btn.setFont(QFont("GmarketSans", 13))
|
|
self.date_btn.setCursor(Qt.PointingHandCursor)
|
|
self.date_btn.clicked.connect(self._show_calendar)
|
|
|
|
self._update_display()
|
|
|
|
self.date_btn.setStyleSheet(f"""
|
|
QPushButton {{
|
|
background-color: {'#1e293b' if is_dark else '#ffffff'};
|
|
color: {'#f8fafc' if is_dark else '#1e293b'};
|
|
border: 2px solid {'#334155' if is_dark else '#e2e8f0'};
|
|
border-radius: 8px;
|
|
padding: 10px 14px;
|
|
text-align: left;
|
|
}}
|
|
QPushButton:hover {{
|
|
border-color: #3b82f6;
|
|
}}
|
|
QPushButton:focus {{
|
|
border-color: #3b82f6;
|
|
}}
|
|
""")
|
|
|
|
layout.addWidget(self.date_btn)
|
|
|
|
def _update_display(self):
|
|
"""날짜 표시 업데이트"""
|
|
if self._date:
|
|
text = f"📅 {self._date.year}.{self._date.month:02d}.{self._date.day:02d}"
|
|
else:
|
|
text = "📅 날짜 선택"
|
|
self.date_btn.setText(text)
|
|
|
|
def _show_calendar(self):
|
|
"""달력 팝업 표시"""
|
|
if self._popup and self._popup.isVisible():
|
|
self._popup.close()
|
|
return
|
|
|
|
self._popup = CalendarPopup(self, range_mode=False)
|
|
self._popup.date_selected.connect(self._on_date_selected)
|
|
self._popup.closed.connect(self._on_popup_closed)
|
|
|
|
if self._date:
|
|
self._popup.set_date(self._date)
|
|
|
|
# 팝업 위치 계산
|
|
pos = self.mapToGlobal(QPoint(0, self.height() + 4))
|
|
self._popup.move(pos)
|
|
self._popup.show()
|
|
|
|
def _on_date_selected(self, selected_date: date):
|
|
"""날짜 선택됨"""
|
|
self._date = selected_date
|
|
self._update_display()
|
|
self.date_changed.emit(selected_date)
|
|
|
|
def _on_popup_closed(self):
|
|
"""팝업 닫힘"""
|
|
self._popup = None
|
|
|
|
def get_date(self) -> Optional[date]:
|
|
"""선택된 날짜 반환"""
|
|
return self._date
|
|
|
|
def set_date(self, d: date):
|
|
"""날짜 설정"""
|
|
self._date = d
|
|
self._update_display()
|
|
|
|
|
|
class CustomDateRangeInput(QWidget):
|
|
"""
|
|
커스텀 기간 입력 필드
|
|
|
|
클릭 시 기간 선택이 가능한 달력 팝업이 표시됩니다.
|
|
|
|
Signals:
|
|
range_changed: 기간 변경 시그널 (start: date, end: date)
|
|
|
|
Examples:
|
|
>>> range_input = CustomDateRangeInput()
|
|
>>> range_input.range_changed.connect(self.on_range_changed)
|
|
"""
|
|
|
|
range_changed = Signal(object, object) # start_date, end_date
|
|
|
|
def __init__(
|
|
self,
|
|
parent=None,
|
|
placeholder: str = "기간 선택",
|
|
initial_start: date = None,
|
|
initial_end: date = None
|
|
):
|
|
super().__init__(parent)
|
|
|
|
self.config = ConfigManager()
|
|
self._start_date = initial_start
|
|
self._end_date = initial_end
|
|
self._popup: Optional[CalendarPopup] = None
|
|
|
|
self._setup_ui(placeholder)
|
|
|
|
def _setup_ui(self, placeholder: str):
|
|
"""UI 설정"""
|
|
layout = QHBoxLayout(self)
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
layout.setSpacing(0)
|
|
|
|
theme = self.config.theme
|
|
is_dark = theme == 'dark'
|
|
|
|
# 기간 표시 버튼 (클릭 가능)
|
|
self.range_btn = QPushButton()
|
|
self.range_btn.setFont(QFont("GmarketSans", 13))
|
|
self.range_btn.setCursor(Qt.PointingHandCursor)
|
|
self.range_btn.clicked.connect(self._show_calendar)
|
|
|
|
self._update_display()
|
|
|
|
self.range_btn.setStyleSheet(f"""
|
|
QPushButton {{
|
|
background-color: {'#1e293b' if is_dark else '#ffffff'};
|
|
color: {'#f8fafc' if is_dark else '#1e293b'};
|
|
border: 2px solid {'#334155' if is_dark else '#e2e8f0'};
|
|
border-radius: 8px;
|
|
padding: 10px 14px;
|
|
text-align: left;
|
|
}}
|
|
QPushButton:hover {{
|
|
border-color: #3b82f6;
|
|
}}
|
|
QPushButton:focus {{
|
|
border-color: #3b82f6;
|
|
}}
|
|
""")
|
|
|
|
layout.addWidget(self.range_btn)
|
|
|
|
def _update_display(self):
|
|
"""기간 표시 업데이트"""
|
|
if self._start_date and self._end_date:
|
|
text = (
|
|
f"📅 {self._start_date.year}.{self._start_date.month:02d}.{self._start_date.day:02d}"
|
|
f" ~ {self._end_date.year}.{self._end_date.month:02d}.{self._end_date.day:02d}"
|
|
)
|
|
else:
|
|
text = "📅 기간 선택"
|
|
self.range_btn.setText(text)
|
|
|
|
def _show_calendar(self):
|
|
"""달력 팝업 표시"""
|
|
if self._popup and self._popup.isVisible():
|
|
self._popup.close()
|
|
return
|
|
|
|
self._popup = CalendarPopup(self, range_mode=True)
|
|
self._popup.range_selected.connect(self._on_range_selected)
|
|
self._popup.closed.connect(self._on_popup_closed)
|
|
|
|
if self._start_date and self._end_date:
|
|
self._popup.set_range(self._start_date, self._end_date)
|
|
|
|
# 팝업 위치 계산
|
|
pos = self.mapToGlobal(QPoint(0, self.height() + 4))
|
|
self._popup.move(pos)
|
|
self._popup.show()
|
|
|
|
def _on_range_selected(self, start: date, end: date):
|
|
"""기간 선택됨"""
|
|
self._start_date = start
|
|
self._end_date = end
|
|
self._update_display()
|
|
self.range_changed.emit(start, end)
|
|
|
|
def _on_popup_closed(self):
|
|
"""팝업 닫힘"""
|
|
self._popup = None
|
|
|
|
def get_range(self) -> Tuple[Optional[date], Optional[date]]:
|
|
"""선택된 기간 반환"""
|
|
return self._start_date, self._end_date
|
|
|
|
def set_range(self, start: date, end: date):
|
|
"""기간 설정"""
|
|
self._start_date = start
|
|
self._end_date = end
|
|
self._update_display()
|
|
|