# -*- 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("날씨 정보를 불러오는 중 오류가 발생했습니다.")