handOver2/ui/dialogs/weather_detail_dialog.py

369 lines
14 KiB
Python

# -*- coding: utf-8 -*-
"""
날씨 상세 다이얼로그 모듈
3일간의 상세 날씨 정보를 표시합니다.
"""
from datetime import datetime
from PySide6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QTableWidget,
QTableWidgetItem, QHeaderView, QScrollArea, QFrame
)
from PySide6.QtCore import Qt
from PySide6.QtGui import QFont
from ui.base.base_dialog import BaseDialog
from core.config import ConfigManager
from core.logger import get_logger
from services.weather_service import WeatherService
logger = get_logger(__name__)
class WeatherDetailDialog(BaseDialog):
"""
날씨 상세 다이얼로그
3일간의 1시간 단위 날씨 정보를 표시합니다.
온도, 체감온도, 강수량, 바람세기 등을 포함합니다.
"""
def __init__(self, parent=None):
self.config = ConfigManager()
self.weather_service = WeatherService()
location_name = self.config.get('weather', 'location_name', '부산')
super().__init__(
parent,
title=f"날씨 상세 정보 - {location_name}",
width=900,
height=700,
resizable=True
)
self._setup_content()
self.add_button("닫기", self.accept, primary=True)
self._load_weather_data()
def _setup_content(self):
"""컨텐츠 설정"""
theme = self.config.theme
is_dark = theme == 'dark'
# 스크롤 영역
scroll = QScrollArea()
scroll.setWidgetResizable(True)
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
# 스크롤 영역 스타일 (다크 테마 지원)
if is_dark:
scroll.setStyleSheet("""
QScrollArea {
background-color: #1e293b;
border: none;
}
QScrollBar:vertical {
background-color: #1e293b;
width: 10px;
border-radius: 5px;
}
QScrollBar::handle:vertical {
background-color: #475569;
border-radius: 5px;
min-height: 20px;
}
QScrollBar::handle:vertical:hover {
background-color: #64748b;
}
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {
height: 0px;
}
""")
else:
scroll.setStyleSheet("""
QScrollArea {
background-color: #ffffff;
border: none;
}
""")
content = QWidget()
# 컨텐츠 위젯 배경색 설정
if is_dark:
content.setStyleSheet("background-color: #1e293b;")
else:
content.setStyleSheet("background-color: #ffffff;")
layout = QVBoxLayout(content)
layout.setContentsMargins(16, 16, 16, 16)
layout.setSpacing(16)
# 현재 날씨 정보
self._create_current_weather_section(layout)
# 구분선
separator = QFrame()
separator.setFrameShape(QFrame.HLine)
separator.setStyleSheet(f"""
QFrame {{
color: {'#334155' if is_dark else '#e2e8f0'};
max-height: 1px;
}}
""")
layout.addWidget(separator)
# 3일간 예보 테이블
self._create_forecast_table(layout)
# 특보 정보
self._create_warning_section(layout)
scroll.setWidget(content)
self.content_layout.addWidget(scroll)
def _create_current_weather_section(self, layout: QVBoxLayout):
"""현재 날씨 섹션 생성"""
theme = self.config.theme
is_dark = theme == 'dark'
current_widget = QWidget()
current_layout = QHBoxLayout(current_widget)
current_layout.setContentsMargins(0, 0, 0, 0)
current_layout.setSpacing(16)
# 현재 날씨 데이터 가져오기
weather_data = self.weather_service.get_last_weather()
logger.debug(f"현재 날씨 데이터: {weather_data}")
# 아이콘
icon_label = QLabel(weather_data.get("icon", "🌤"))
icon_label.setFont(QFont("Segoe UI Emoji", 48))
current_layout.addWidget(icon_label)
# 정보
info_widget = QWidget()
info_layout = QVBoxLayout(info_widget)
info_layout.setContentsMargins(0, 0, 0, 0)
info_layout.setSpacing(4)
# 온도 표시 (기온(체감온도) 형식)
temp = weather_data.get('temp')
feels_like = weather_data.get("feels_like")
# 데이터 검증 및 안전한 표시
if temp is not None and isinstance(temp, (int, float)):
if feels_like is not None and isinstance(feels_like, (int, float)):
temp_text = f"{temp}°C({feels_like}°C)"
else:
temp_text = f"{temp}°C"
else:
temp_text = "--°C"
temp_label = QLabel(temp_text)
temp_label.setFont(QFont("GmarketSans", 32, QFont.Bold))
temp_label.setStyleSheet(f"color: {'#f8fafc' if is_dark else '#1e293b'};")
info_layout.addWidget(temp_label)
condition_label = QLabel(weather_data.get("condition", "정보 없음"))
condition_label.setFont(QFont("GmarketSans", 16))
condition_label.setStyleSheet(f"color: {'#94a3b8' if is_dark else '#64748b'};")
info_layout.addWidget(condition_label)
# 추가 정보
detail_layout = QHBoxLayout()
detail_layout.setSpacing(16)
humidity = weather_data.get("humidity")
wind_speed = weather_data.get("wind_speed", "--")
precipitation_prob = weather_data.get("precipitation_prob")
# 습도
if humidity is not None and isinstance(humidity, (int, float)):
humidity_label = QLabel(f"습도: {humidity}%")
humidity_label.setFont(QFont("GmarketSans", 12))
humidity_label.setStyleSheet(f"color: {'#94a3b8' if is_dark else '#64748b'};")
detail_layout.addWidget(humidity_label)
# 강수확률
if precipitation_prob is not None and isinstance(precipitation_prob, (int, float)):
prob_label = QLabel(f"강수확률: {precipitation_prob}%")
prob_label.setFont(QFont("GmarketSans", 12))
prob_label.setStyleSheet(f"color: {'#94a3b8' if is_dark else '#64748b'};")
detail_layout.addWidget(prob_label)
# 바람
if wind_speed and wind_speed != "--":
wind_label = QLabel(f"바람: {wind_speed}")
wind_label.setFont(QFont("GmarketSans", 12))
wind_label.setStyleSheet(f"color: {'#94a3b8' if is_dark else '#64748b'};")
detail_layout.addWidget(wind_label)
detail_layout.addStretch()
info_layout.addLayout(detail_layout)
current_layout.addWidget(info_widget)
current_layout.addStretch()
layout.addWidget(current_widget)
def _create_forecast_table(self, layout: QVBoxLayout):
"""예보 테이블 생성"""
theme = self.config.theme
is_dark = theme == 'dark'
# 테이블 생성
self.forecast_table = QTableWidget()
self.forecast_table.setColumnCount(6)
self.forecast_table.setHorizontalHeaderLabels([
"날짜/시간", "온도", "체감온도", "강수확률", "바람", "날씨"
])
self.forecast_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.forecast_table.verticalHeader().setVisible(False)
self.forecast_table.setSelectionMode(QTableWidget.NoSelection)
self.forecast_table.setEditTriggers(QTableWidget.NoEditTriggers)
# 테이블 스타일
if is_dark:
self.forecast_table.setStyleSheet("""
QTableWidget {
background-color: #1e293b;
color: #f8fafc;
border: 1px solid #334155;
border-radius: 8px;
gridline-color: #334155;
}
QTableWidget::item {
padding: 8px;
}
QHeaderView::section {
background-color: #334155;
color: #f8fafc;
padding: 8px;
border: none;
font-weight: bold;
}
""")
else:
self.forecast_table.setStyleSheet("""
QTableWidget {
background-color: #ffffff;
color: #1e293b;
border: 1px solid #e2e8f0;
border-radius: 8px;
gridline-color: #e2e8f0;
}
QTableWidget::item {
padding: 8px;
}
QHeaderView::section {
background-color: #e2e8f0;
color: #1e293b;
padding: 8px;
border: none;
font-weight: bold;
}
""")
layout.addWidget(self.forecast_table)
def _create_warning_section(self, layout: QVBoxLayout):
"""특보 정보 섹션 생성"""
theme = self.config.theme
is_dark = theme == 'dark'
warning_label = QLabel("특보 정보")
warning_label.setFont(QFont("GmarketSans", 14, QFont.Bold))
warning_label.setStyleSheet(f"color: {'#f8fafc' if is_dark else '#1e293b'};")
layout.addWidget(warning_label)
self.warning_text = QLabel("특보 정보가 없습니다.")
self.warning_text.setFont(QFont("GmarketSans", 12))
self.warning_text.setStyleSheet(f"""
color: {'#94a3b8' if is_dark else '#64748b'};
padding: 8px;
border: 1px solid {'#334155' if is_dark else '#e2e8f0'};
border-radius: 4px;
""")
self.warning_text.setWordWrap(True)
layout.addWidget(self.warning_text)
def _load_weather_data(self):
"""날씨 데이터 로드"""
try:
# WeatherService에서 모든 날씨 아이템 가져오기
weather_items = self.weather_service.get_all_weather_items()
if not weather_items:
# 데이터가 없으면 메시지 표시
self.forecast_table.setRowCount(1)
no_data_item = QTableWidgetItem("날씨 데이터를 불러올 수 없습니다. 잠시 후 다시 시도해주세요.")
no_data_item.setTextAlignment(Qt.AlignCenter)
self.forecast_table.setItem(0, 0, no_data_item)
self.forecast_table.setSpan(0, 0, 1, 6)
self.warning_text.setText("날씨 정보를 불러올 수 없습니다.")
return
# 최대 3일치 데이터만 표시 (약 72시간)
max_items = min(len(weather_items), 72)
display_items = weather_items[:max_items]
# 테이블에 데이터 추가
self.forecast_table.setRowCount(len(display_items))
for row_idx, item in enumerate(display_items):
# 날짜/시간
dt = item.get('datetime')
if dt:
dt_str = dt.strftime("%m/%d %H:%M")
else:
date_str = item.get('date', '')
time_str = item.get('time', '')
dt_str = f"{date_str} {time_str}" if date_str and time_str else "-"
dt_item = QTableWidgetItem(dt_str)
self.forecast_table.setItem(row_idx, 0, dt_item)
# 온도
temp = item.get('temp')
temp_str = f"{temp}°C" if temp is not None else "-"
temp_item = QTableWidgetItem(temp_str)
self.forecast_table.setItem(row_idx, 1, temp_item)
# 체감온도
feels_like = item.get('feels_like')
feels_str = f"{feels_like}°C" if feels_like is not None else "-"
feels_item = QTableWidgetItem(feels_str)
self.forecast_table.setItem(row_idx, 2, feels_item)
# 강수확률 (강수량 대신 강수확률 표시)
prob = item.get('precipitation_prob')
prob_str = f"{prob}%" if prob is not None else "-"
prec_item = QTableWidgetItem(prob_str)
self.forecast_table.setItem(row_idx, 3, prec_item)
# 바람
wind = item.get('wind', '-')
wind_str = str(wind) if wind != '-' else "-"
wind_item = QTableWidgetItem(wind_str)
self.forecast_table.setItem(row_idx, 4, wind_item)
# 날씨
weather = item.get('weather', '정보 없음')
icon = item.get('icon', '🌤')
weather_str = f"{icon} {weather}"
weather_item = QTableWidgetItem(weather_str)
self.forecast_table.setItem(row_idx, 5, weather_item)
# 특보 정보
self.warning_text.setText("현재 특보 정보가 없습니다.")
except Exception as e:
logger.error(f"날씨 데이터 로드 실패: {e}")
self.forecast_table.setRowCount(1)
error_item = QTableWidgetItem(f"오류: {str(e)}")
error_item.setTextAlignment(Qt.AlignCenter)
self.forecast_table.setItem(0, 0, error_item)
self.forecast_table.setSpan(0, 0, 1, 6)
self.warning_text.setText("날씨 정보를 불러오는 중 오류가 발생했습니다.")