handOver2/ui/components/custom_calendar.py

852 lines
30 KiB
Python

# -*- coding: utf-8 -*-
"""
커스텀 캘린더 모듈
날짜 및 기간 선택을 위한 커스텀 캘린더 위젯입니다.
기능:
- 단일 날짜 선택
- 기간(시작~종료) 선택 토글
- 시간 선택 (시/분, 30분 단위)
- 날짜 강조 표시
- 현대적인 UI 스타일
"""
from datetime import date, datetime, time
from typing import Optional, Tuple
from PySide6.QtWidgets import (
QCalendarWidget, QWidget, QVBoxLayout, QHBoxLayout,
QPushButton, QLabel, QSpinBox, QComboBox
)
from PySide6.QtCore import Qt, Signal, QDate, QTime
from PySide6.QtGui import QFont, QColor, QTextCharFormat, QPainter, QPen, QBrush
from core.config import ConfigManager
from core.logger import get_logger
logger = get_logger(__name__)
class RangeCalendarWidget(QCalendarWidget):
"""
기간 선택을 지원하는 캘린더 위젯
paintCell을 오버라이드하여 기간 범위를 시각적으로 표시합니다.
"""
def __init__(self, parent=None):
super().__init__(parent)
self.config = ConfigManager()
self._start_date: Optional[QDate] = None
self._end_date: Optional[QDate] = None
self._range_mode = False
# 기본 설정
self.setVerticalHeaderFormat(QCalendarWidget.NoVerticalHeader)
self.setHorizontalHeaderFormat(QCalendarWidget.ShortDayNames)
self.setGridVisible(False)
self.setNavigationBarVisible(True)
self._apply_style()
def set_range_mode(self, enabled: bool):
"""기간 선택 모드 설정"""
self._range_mode = enabled
if not enabled:
self._start_date = None
self._end_date = None
self.updateCells()
def set_range(self, start: Optional[QDate], end: Optional[QDate]):
"""기간 설정"""
self._start_date = start
self._end_date = end
self.updateCells()
def get_range(self) -> Tuple[Optional[date], Optional[date]]:
"""선택된 기간 반환"""
start = None
end = None
if self._start_date:
start = date(self._start_date.year(), self._start_date.month(), self._start_date.day())
if self._end_date:
end = date(self._end_date.year(), self._end_date.month(), self._end_date.day())
return start, end
def paintCell(self, painter: QPainter, rect, qdate: QDate):
"""셀 그리기 (기간 범위 표시)"""
painter.save()
theme = self.config.theme
is_dark = theme == 'dark'
# 색상 정의
if is_dark:
text_color = QColor("#f8fafc")
range_bg = QColor("#3b82f6")
range_bg.setAlpha(60)
start_end_bg = QColor("#3b82f6")
today_border = QColor("#22c55e")
other_month_text = QColor("#64748b")
else:
text_color = QColor("#1e293b")
range_bg = QColor("#3b82f6")
range_bg.setAlpha(40)
start_end_bg = QColor("#3b82f6")
today_border = QColor("#22c55e")
other_month_text = QColor("#94a3b8")
# 현재 보이는 달인지 확인
is_current_month = qdate.month() == self.monthShown() and qdate.year() == self.yearShown()
# 기간 모드에서 범위 내 날짜인지 확인
in_range = False
is_start = False
is_end = False
if self._range_mode and self._start_date and self._end_date:
start = self._start_date
end = self._end_date
if start > end:
start, end = end, start
in_range = start <= qdate <= end
is_start = qdate == start
is_end = qdate == end
elif self._range_mode and self._start_date:
is_start = qdate == self._start_date
# 배경 그리기
if is_start or is_end:
# 시작/종료 날짜: 원형 배경
painter.setBrush(QBrush(start_end_bg))
painter.setPen(Qt.NoPen)
center = rect.center()
radius = min(rect.width(), rect.height()) // 2 - 4
painter.drawEllipse(center, radius, radius)
text_color = QColor("#ffffff")
elif in_range:
# 범위 내 날짜: 연한 배경
painter.fillRect(rect, range_bg)
# 오늘 날짜 테두리
if qdate == QDate.currentDate():
painter.setPen(QPen(today_border, 2))
painter.setBrush(Qt.NoBrush)
center = rect.center()
radius = min(rect.width(), rect.height()) // 2 - 4
painter.drawEllipse(center, radius, radius)
# 텍스트 그리기
if not is_current_month:
painter.setPen(other_month_text)
elif is_start or is_end:
painter.setPen(QColor("#ffffff"))
else:
painter.setPen(text_color)
painter.setFont(QFont("GmarketSans", 11))
painter.drawText(rect, Qt.AlignCenter, str(qdate.day()))
painter.restore()
def _apply_style(self):
"""스타일 적용"""
theme = self.config.theme
if theme == 'dark':
bg = "#1e293b"
header_bg = "#334155"
text = "#f8fafc"
hover_bg = "#475569"
nav_bg = "#0f172a"
border = "#334155"
else:
bg = "#ffffff"
header_bg = "#f1f5f9"
text = "#1e293b"
hover_bg = "#e2e8f0"
nav_bg = "#f8fafc"
border = "#e2e8f0"
self.setStyleSheet(f"""
QCalendarWidget {{
background-color: {bg};
font-family: 'GmarketSans';
border: 1px solid {border};
border-radius: 8px;
}}
QCalendarWidget QToolButton {{
background-color: {nav_bg};
color: {text};
font-size: 13px;
font-weight: bold;
padding: 6px 12px;
border-radius: 6px;
margin: 2px;
}}
QCalendarWidget QToolButton:hover {{
background-color: {hover_bg};
}}
QCalendarWidget QToolButton::menu-indicator {{
image: none;
}}
QCalendarWidget QWidget#qt_calendar_navigationbar {{
background-color: {nav_bg};
padding: 4px;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}}
QCalendarWidget QTableView {{
background-color: {bg};
outline: none;
selection-background-color: transparent;
selection-color: {text};
}}
QCalendarWidget QHeaderView::section {{
background-color: {header_bg};
color: {text};
padding: 6px;
border: none;
font-weight: bold;
font-size: 11px;
}}
""")
class TimeSelector(QWidget):
"""
시간 선택 위젯
시간과 분을 선택할 수 있습니다.
분은 30분 단위 (00, 30)로 기본 설정됩니다.
Signals:
time_changed: 시간이 변경되었을 때 (QTime)
"""
time_changed = Signal(object) # QTime
def __init__(self, parent=None, minute_step: int = 30):
"""
Args:
parent: 부모 위젯
minute_step: 분 단위 (기본 30분)
"""
super().__init__(parent)
self.config = ConfigManager()
self._minute_step = minute_step
self._setup_ui()
def _setup_ui(self):
"""UI 설정"""
layout = QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(4)
theme = self.config.theme
is_dark = theme == 'dark'
# 시간 선택
self.hour_spin = QSpinBox()
self.hour_spin.setRange(0, 23)
self.hour_spin.setSuffix("")
self.hour_spin.setFont(QFont("GmarketSans", 12))
self.hour_spin.valueChanged.connect(self._on_time_changed)
layout.addWidget(self.hour_spin)
# 구분자
colon = QLabel(":")
colon.setFont(QFont("GmarketSans", 14, QFont.Bold))
colon.setStyleSheet(f"color: {'#f8fafc' if is_dark else '#1e293b'};")
layout.addWidget(colon)
# 분 선택 (스텝이 1이면 스핀박스, 아니면 드롭다운)
if self._minute_step == 1:
self.minute_spin = QSpinBox()
self.minute_spin.setRange(0, 59)
self.minute_spin.setSuffix("")
self.minute_spin.setFont(QFont("GmarketSans", 12))
self.minute_spin.valueChanged.connect(self._on_time_changed)
self.minute_combo = None
layout.addWidget(self.minute_spin)
else:
self.minute_spin = None
self.minute_combo = QComboBox()
self.minute_combo.setFont(QFont("GmarketSans", 12))
# 분 옵션 생성 (30분 단위)
minutes = []
for m in range(0, 60, self._minute_step):
minutes.append(f"{m:02d}")
self.minute_combo.addItems(minutes)
self.minute_combo.currentIndexChanged.connect(self._on_time_changed)
layout.addWidget(self.minute_combo)
self._apply_style()
def _apply_style(self):
"""스타일 적용"""
theme = self.config.theme
if theme == 'dark':
bg = "#1e293b"
text = "#f8fafc"
border = "#475569"
hover = "#334155"
else:
bg = "#ffffff"
text = "#1e293b"
border = "#e2e8f0"
hover = "#f1f5f9"
style = f"""
QSpinBox, QComboBox {{
background-color: {bg};
color: {text};
border: 1px solid {border};
border-radius: 6px;
padding: 6px 10px;
min-width: 70px;
}}
QSpinBox:hover, QComboBox:hover {{
border-color: #3b82f6;
}}
QSpinBox::up-button, QSpinBox::down-button {{
width: 20px;
background-color: {hover};
border: none;
}}
QSpinBox::up-button:hover, QSpinBox::down-button:hover {{
background-color: #3b82f6;
}}
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 {text};
margin-right: 5px;
}}
QComboBox QAbstractItemView {{
background-color: {bg};
color: {text};
border: 1px solid {border};
selection-background-color: #3b82f6;
}}
"""
self.setStyleSheet(style)
def _on_time_changed(self):
"""시간 변경 시"""
current_time = self.get_time()
self.time_changed.emit(current_time)
def get_time(self) -> time:
"""현재 선택된 시간 반환"""
hour = self.hour_spin.value()
if self._minute_step == 1 and self.minute_spin:
minute = self.minute_spin.value()
else:
minute_idx = self.minute_combo.currentIndex()
minute = minute_idx * self._minute_step
return time(hour, minute)
def set_time(self, t: time):
"""시간 설정"""
self.hour_spin.setValue(t.hour)
if self._minute_step == 1 and self.minute_spin:
self.minute_spin.setValue(t.minute)
else:
# 가장 가까운 분 단위로 반올림
minute_idx = round(t.minute / self._minute_step)
if minute_idx >= self.minute_combo.count():
minute_idx = 0
self.minute_combo.setCurrentIndex(minute_idx)
def get_qtime(self) -> QTime:
"""QTime으로 반환"""
t = self.get_time()
return QTime(t.hour, t.minute)
class CustomCalendar(QWidget):
"""
커스텀 캘린더 위젯
날짜 또는 기간 선택 기능을 제공합니다.
'기간' 토글을 활성화하면 시작일과 종료일을 선택할 수 있습니다.
시간 선택 옵션을 활성화하면 시/분도 선택할 수 있습니다.
Signals:
date_selected: 단일 날짜 선택 시그널 (date)
range_selected: 기간 선택 시그널 (start: date, end: date)
datetime_selected: 날짜+시간 선택 시그널 (datetime)
range_datetime_selected: 기간+시간 선택 시그널 (start: datetime, end: datetime)
Examples:
>>> calendar = CustomCalendar(show_time=True)
>>> calendar.date_selected.connect(self.on_date_selected)
>>> calendar.datetime_selected.connect(self.on_datetime_selected)
"""
date_selected = Signal(object) # date
range_selected = Signal(object, object) # start_date, end_date
datetime_selected = Signal(object) # datetime
range_datetime_selected = Signal(object, object) # start_datetime, end_datetime
def __init__(
self,
parent=None,
show_range_toggle: bool = True,
show_time: bool = False,
minute_step: int = 30
):
"""
Args:
parent: 부모 위젯
show_range_toggle: 기간 토글 버튼 표시 여부
show_time: 시간 선택 표시 여부
minute_step: 분 단위 (기본 30분)
"""
super().__init__(parent)
self.config = ConfigManager()
self._show_range_toggle = show_range_toggle
self._show_time = show_time
self._minute_step = minute_step
self._range_mode = False
self._first_click = True # 기간 모드에서 첫 번째 클릭인지
self._start_date: Optional[QDate] = None
self._end_date: Optional[QDate] = None
self._setup_ui()
self._connect_signals()
def _setup_ui(self):
"""UI 설정"""
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(8)
theme = self.config.theme
is_dark = theme == 'dark'
# 상단 툴바 (기간 토글, 날짜 표시)
toolbar = QWidget()
toolbar_layout = QHBoxLayout(toolbar)
toolbar_layout.setContentsMargins(8, 4, 8, 4)
toolbar_layout.setSpacing(8)
# 선택된 날짜/기간 표시
self.date_label = QLabel("날짜를 선택하세요")
self.date_label.setFont(QFont("GmarketSans", 11))
self.date_label.setStyleSheet(f"color: {'#94a3b8' if is_dark else '#64748b'};")
toolbar_layout.addWidget(self.date_label)
toolbar_layout.addStretch()
# 기간 토글 버튼
if self._show_range_toggle:
self.range_btn = QPushButton("📅 기간")
self.range_btn.setCheckable(True)
self.range_btn.setFont(QFont("GmarketSans", 11))
self.range_btn.setCursor(Qt.PointingHandCursor)
self.range_btn.clicked.connect(self._toggle_range_mode)
btn_style = f"""
QPushButton {{
background-color: {'#334155' if is_dark else '#e2e8f0'};
color: {'#f8fafc' if is_dark else '#1e293b'};
border: none;
border-radius: 6px;
padding: 6px 12px;
}}
QPushButton:hover {{
background-color: {'#475569' if is_dark else '#cbd5e1'};
}}
QPushButton:checked {{
background-color: #3b82f6;
color: white;
}}
"""
self.range_btn.setStyleSheet(btn_style)
toolbar_layout.addWidget(self.range_btn)
else:
self.range_btn = None
layout.addWidget(toolbar)
# 캘린더
self.calendar = RangeCalendarWidget()
layout.addWidget(self.calendar)
# 시간 선택 영역 (옵션)
if self._show_time:
time_container = QWidget()
time_layout = QHBoxLayout(time_container)
time_layout.setContentsMargins(8, 8, 8, 8)
time_layout.setSpacing(16)
# 시작 시간 (기간 모드)
self.start_time_label = QLabel("시작 시간:")
self.start_time_label.setFont(QFont("GmarketSans", 11))
self.start_time_label.setStyleSheet(f"color: {'#f8fafc' if is_dark else '#1e293b'};")
self.start_time_label.setVisible(False)
time_layout.addWidget(self.start_time_label)
self.start_time_selector = TimeSelector(minute_step=self._minute_step)
self.start_time_selector.time_changed.connect(self._on_time_changed)
time_layout.addWidget(self.start_time_selector)
# 종료 시간 (기간 모드)
self.end_time_label = QLabel("→ 종료 시간:")
self.end_time_label.setFont(QFont("GmarketSans", 11))
self.end_time_label.setStyleSheet(f"color: {'#f8fafc' if is_dark else '#1e293b'};")
self.end_time_label.setVisible(False)
time_layout.addWidget(self.end_time_label)
self.end_time_selector = TimeSelector(minute_step=self._minute_step)
self.end_time_selector.time_changed.connect(self._on_time_changed)
self.end_time_selector.setVisible(False)
time_layout.addWidget(self.end_time_selector)
time_layout.addStretch()
layout.addWidget(time_container)
else:
self.start_time_selector = None
self.end_time_selector = None
# 하단 버튼
btn_container = QWidget()
btn_layout = QHBoxLayout(btn_container)
btn_layout.setContentsMargins(8, 4, 8, 8)
btn_layout.setSpacing(8)
# 오늘 버튼
self.today_btn = QPushButton("오늘")
self.today_btn.setFont(QFont("GmarketSans", 11))
self.today_btn.setCursor(Qt.PointingHandCursor)
self.today_btn.clicked.connect(self._go_to_today)
self.today_btn.setStyleSheet(f"""
QPushButton {{
background-color: {'#334155' if is_dark else '#e2e8f0'};
color: {'#f8fafc' if is_dark else '#1e293b'};
border: none;
border-radius: 6px;
padding: 6px 16px;
}}
QPushButton:hover {{
background-color: {'#475569' if is_dark else '#cbd5e1'};
}}
""")
btn_layout.addWidget(self.today_btn)
btn_layout.addStretch()
# 초기화 버튼
self.clear_btn = QPushButton("초기화")
self.clear_btn.setFont(QFont("GmarketSans", 11))
self.clear_btn.setCursor(Qt.PointingHandCursor)
self.clear_btn.clicked.connect(self._clear_selection)
self.clear_btn.setStyleSheet(f"""
QPushButton {{
background-color: {'#334155' if is_dark else '#e2e8f0'};
color: {'#f8fafc' if is_dark else '#1e293b'};
border: none;
border-radius: 6px;
padding: 6px 16px;
}}
QPushButton:hover {{
background-color: {'#475569' if is_dark else '#cbd5e1'};
}}
""")
btn_layout.addWidget(self.clear_btn)
layout.addWidget(btn_container)
def _connect_signals(self):
"""시그널 연결"""
self.calendar.clicked.connect(self._on_date_clicked)
def _toggle_range_mode(self):
"""기간 선택 모드 토글"""
if self.range_btn:
self._range_mode = self.range_btn.isChecked()
else:
self._range_mode = not self._range_mode
self._first_click = True
self._start_date = None
self._end_date = None
self.calendar.set_range_mode(self._range_mode)
# 시간 선택기 표시/숨김
if self._show_time:
self.start_time_label.setVisible(self._range_mode)
self.end_time_label.setVisible(self._range_mode)
self.end_time_selector.setVisible(self._range_mode)
self._update_date_label()
def set_range_mode(self, enabled: bool):
"""외부에서 기간 모드 설정"""
if self.range_btn:
self.range_btn.setChecked(enabled)
self._range_mode = enabled
self.calendar.set_range_mode(enabled)
if self._show_time:
self.start_time_label.setVisible(enabled)
self.end_time_label.setVisible(enabled)
self.end_time_selector.setVisible(enabled)
self._update_date_label()
def _on_date_clicked(self, qdate: QDate):
"""날짜 클릭 이벤트"""
if self._range_mode:
# 기간 선택 모드
if self._first_click:
# 첫 번째 클릭: 시작일 설정
self._start_date = qdate
self._end_date = None
self._first_click = False
self.calendar.set_range(self._start_date, None)
else:
# 두 번째 클릭: 종료일 설정
self._end_date = qdate
self._first_click = True
# 시작일이 종료일보다 크면 스왑
if self._start_date > self._end_date:
self._start_date, self._end_date = self._end_date, self._start_date
self.calendar.set_range(self._start_date, self._end_date)
# 시그널 발생
start = date(self._start_date.year(), self._start_date.month(), self._start_date.day())
end = date(self._end_date.year(), self._end_date.month(), self._end_date.day())
if self._show_time:
start_time = self.start_time_selector.get_time()
end_time = self.end_time_selector.get_time()
start_dt = datetime.combine(start, start_time)
end_dt = datetime.combine(end, end_time)
self.range_datetime_selected.emit(start_dt, end_dt)
else:
self.range_selected.emit(start, end)
else:
# 단일 날짜 선택 모드
selected_date = date(qdate.year(), qdate.month(), qdate.day())
if self._show_time:
selected_time = self.start_time_selector.get_time()
selected_dt = datetime.combine(selected_date, selected_time)
self.datetime_selected.emit(selected_dt)
else:
self.date_selected.emit(selected_date)
self._update_date_label()
def _on_time_changed(self, _time):
"""시간 변경 시"""
# 날짜가 선택된 상태에서 시간만 변경되면 시그널 발생
if self._range_mode:
if self._start_date and self._end_date:
start = date(self._start_date.year(), self._start_date.month(), self._start_date.day())
end = date(self._end_date.year(), self._end_date.month(), self._end_date.day())
start_time = self.start_time_selector.get_time()
end_time = self.end_time_selector.get_time()
start_dt = datetime.combine(start, start_time)
end_dt = datetime.combine(end, end_time)
self.range_datetime_selected.emit(start_dt, end_dt)
else:
qdate = self.calendar.selectedDate()
selected_date = date(qdate.year(), qdate.month(), qdate.day())
selected_time = self.start_time_selector.get_time()
selected_dt = datetime.combine(selected_date, selected_time)
self.datetime_selected.emit(selected_dt)
self._update_date_label()
def _update_date_label(self):
"""날짜 라벨 업데이트"""
theme = self.config.theme
is_dark = theme == 'dark'
if self._range_mode:
if self._start_date and self._end_date:
start = self._start_date
end = self._end_date
text = f"{start.year()}.{start.month():02d}.{start.day():02d}"
if self._show_time:
st = self.start_time_selector.get_time()
text += f" {st.hour:02d}:{st.minute:02d}"
text += f" ~ {end.year()}.{end.month():02d}.{end.day():02d}"
if self._show_time:
et = self.end_time_selector.get_time()
text += f" {et.hour:02d}:{et.minute:02d}"
self.date_label.setStyleSheet(f"color: {'#f8fafc' if is_dark else '#1e293b'}; font-weight: bold;")
elif self._start_date:
text = f"{self._start_date.year()}.{self._start_date.month():02d}.{self._start_date.day():02d} ~ ?"
self.date_label.setStyleSheet(f"color: {'#f8fafc' if is_dark else '#1e293b'};")
else:
text = "시작일을 선택하세요"
self.date_label.setStyleSheet(f"color: {'#94a3b8' if is_dark else '#64748b'};")
else:
qdate = self.calendar.selectedDate()
text = f"{qdate.year()}.{qdate.month():02d}.{qdate.day():02d}"
if self._show_time:
t = self.start_time_selector.get_time()
text += f" {t.hour:02d}:{t.minute:02d}"
self.date_label.setStyleSheet(f"color: {'#f8fafc' if is_dark else '#1e293b'}; font-weight: bold;")
self.date_label.setText(text)
def _go_to_today(self):
"""오늘 날짜로 이동"""
self.calendar.setSelectedDate(QDate.currentDate())
if not self._range_mode:
today = date.today()
if self._show_time:
now = datetime.now()
self.start_time_selector.set_time(now.time())
self.datetime_selected.emit(now)
else:
self.date_selected.emit(today)
self._update_date_label()
def _clear_selection(self):
"""선택 초기화"""
self._first_click = True
self._start_date = None
self._end_date = None
self.calendar.set_range(None, None)
if self._show_time:
self.start_time_selector.set_time(time(0, 0))
if self.end_time_selector:
self.end_time_selector.set_time(time(0, 0))
self._update_date_label()
def set_date(self, d: date):
"""날짜 설정"""
self.calendar.setSelectedDate(QDate(d.year, d.month, d.day))
self._update_date_label()
def get_date(self) -> date:
"""선택된 날짜 반환"""
qdate = self.calendar.selectedDate()
return date(qdate.year(), qdate.month(), qdate.day())
def set_datetime(self, dt: datetime):
"""날짜와 시간 설정"""
self.calendar.setSelectedDate(QDate(dt.year, dt.month, dt.day))
if self._show_time:
self.start_time_selector.set_time(dt.time())
self._update_date_label()
def get_datetime(self) -> datetime:
"""선택된 날짜+시간 반환"""
qdate = self.calendar.selectedDate()
d = date(qdate.year(), qdate.month(), qdate.day())
if self._show_time:
t = self.start_time_selector.get_time()
else:
t = time(0, 0)
return datetime.combine(d, t)
def set_range(self, start: date, end: date):
"""기간 설정"""
self._range_mode = True
if self.range_btn:
self.range_btn.setChecked(True)
self._start_date = QDate(start.year, start.month, start.day)
self._end_date = QDate(end.year, end.month, end.day)
self._first_click = True
self.calendar.set_range_mode(True)
self.calendar.set_range(self._start_date, self._end_date)
if self._show_time:
self.start_time_label.setVisible(True)
self.end_time_label.setVisible(True)
self.end_time_selector.setVisible(True)
self._update_date_label()
def set_range_datetime(self, start: datetime, end: datetime):
"""기간 + 시간 설정"""
self.set_range(start.date(), end.date())
if self._show_time:
self.start_time_selector.set_time(start.time())
self.end_time_selector.set_time(end.time())
self._update_date_label()
def get_range(self) -> Tuple[Optional[date], Optional[date]]:
"""선택된 기간 반환"""
return self.calendar.get_range()
def get_range_datetime(self) -> Tuple[Optional[datetime], Optional[datetime]]:
"""선택된 기간 + 시간 반환"""
start_date, end_date = self.calendar.get_range()
if start_date and end_date:
if self._show_time:
start_time = self.start_time_selector.get_time()
end_time = self.end_time_selector.get_time()
else:
start_time = time(0, 0)
end_time = time(23, 59)
return (
datetime.combine(start_date, start_time),
datetime.combine(end_date, end_time)
)
return None, None
def highlight_dates(self, dates: list, color: str = "#3b82f6"):
"""
여러 날짜 강조 표시
Args:
dates: date 리스트
color: 강조 색상
"""
highlight_format = QTextCharFormat()
highlight_format.setBackground(QColor(color))
highlight_format.setForeground(QColor("#ffffff"))
for d in dates:
self.calendar.setDateTextFormat(QDate(d.year, d.month, d.day), highlight_format)