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