handOver2/ui/dialogs/settings_dialog.py

876 lines
30 KiB
Python

# -*- coding: utf-8 -*-
"""
설정 다이얼로그 모듈
환경설정을 위한 다이얼로그입니다.
"""
from PySide6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QTabWidget,
QLabel, QFrame, QScrollArea, QSpinBox, QComboBox,
QGridLayout, QGroupBox, QPushButton, QTableWidget,
QTableWidgetItem, QHeaderView
)
from PySide6.QtCore import Qt
from PySide6.QtGui import QFont, QColor
from ui.base.base_dialog import BaseDialog
from ui.components.custom_input import CustomLineEdit, CustomComboBox, LabeledInput
from ui.components.toggle_switch import LabeledToggle
from ui.components.custom_button import CustomButton
from core.config import ConfigManager
from core.signals import GlobalSignals
from core.constants import FONT_FAMILY
from core.logger import get_logger
logger = get_logger(__name__)
# 사용 가능한 폰트 목록
AVAILABLE_FONTS = [
"GmarketSans",
"Pretendard",
"Noto Sans KR",
"Spoqa Han Sans Neo",
"맑은 고딕",
"나눔고딕",
"Arial",
"Segoe UI",
]
# 폰트 굵기 옵션
FONT_WEIGHTS = ["normal", "medium", "bold"]
class FontSettingRow(QWidget):
"""폰트 설정 행 위젯"""
def __init__(
self,
label: str,
initial_family: str = FONT_FAMILY,
initial_size: int = 13,
initial_weight: str = "normal",
parent=None
):
super().__init__(parent)
self.config = ConfigManager()
theme = self.config.theme
is_dark = theme == 'dark'
layout = QHBoxLayout(self)
layout.setContentsMargins(0, 4, 0, 4)
layout.setSpacing(8)
# 레이블
self.label = QLabel(label)
self.label.setFont(QFont("GmarketSans", 12))
self.label.setFixedWidth(100)
self.label.setStyleSheet(f"color: {'#f8fafc' if is_dark else '#1e293b'};")
layout.addWidget(self.label)
# 폰트 패밀리
self.family_combo = QComboBox()
self.family_combo.addItems(AVAILABLE_FONTS)
self.family_combo.setCurrentText(initial_family)
self.family_combo.setFixedWidth(150)
self.family_combo.setFont(QFont("GmarketSans", 11))
layout.addWidget(self.family_combo)
# 폰트 크기
self.size_spin = QSpinBox()
self.size_spin.setRange(8, 32)
self.size_spin.setValue(initial_size)
self.size_spin.setSuffix("px")
self.size_spin.setFixedWidth(70)
self.size_spin.setFont(QFont("GmarketSans", 11))
layout.addWidget(self.size_spin)
# 폰트 굵기
self.weight_combo = QComboBox()
self.weight_combo.addItems(FONT_WEIGHTS)
self.weight_combo.setCurrentText(initial_weight)
self.weight_combo.setFixedWidth(80)
self.weight_combo.setFont(QFont("GmarketSans", 11))
layout.addWidget(self.weight_combo)
layout.addStretch()
self._apply_style()
def _apply_style(self):
"""스타일 적용"""
theme = self.config.theme
if theme == 'dark':
bg = "#1e293b"
text = "#f8fafc"
border = "#475569"
else:
bg = "#ffffff"
text = "#1e293b"
border = "#e2e8f0"
style = f"""
QComboBox, QSpinBox {{
background-color: {bg};
color: {text};
border: 1px solid {border};
border-radius: 6px;
padding: 4px 8px;
}}
QComboBox:hover, QSpinBox:hover {{
border-color: #3b82f6;
}}
QComboBox::drop-down {{
border: none;
width: 20px;
}}
QComboBox QAbstractItemView {{
background-color: {bg};
color: {text};
border: 1px solid {border};
selection-background-color: #3b82f6;
}}
"""
self.setStyleSheet(style)
def get_values(self):
"""현재 값 반환"""
return {
"family": self.family_combo.currentText(),
"size": self.size_spin.value(),
"weight": self.weight_combo.currentText(),
}
def set_values(self, family: str, size: int, weight: str):
"""값 설정"""
self.family_combo.setCurrentText(family)
self.size_spin.setValue(size)
self.weight_combo.setCurrentText(weight)
class SettingsDialog(BaseDialog):
"""
설정 다이얼로그
애플리케이션 환경설정을 위한 다이얼로그입니다.
"""
def __init__(self, parent=None):
super().__init__(parent, title="환경설정", width=700, height=600, resizable=True)
self._font_settings = {} # 영역별 폰트 설정 위젯 저장
self._setup_tabs()
self.add_confirm_cancel_buttons("저장", "취소")
def _setup_tabs(self):
"""탭 설정"""
self.tabs = QTabWidget()
self.tabs.setFont(QFont("GmarketSans", 12))
# 일반 탭
self.tabs.addTab(self._create_general_tab(), "일반")
# 레이아웃 탭
self.tabs.addTab(self._create_layout_tab(), "레이아웃")
# UI 폰트 탭
self.tabs.addTab(self._create_font_tab(), "폰트 설정")
# 편성 설정 탭
self.tabs.addTab(self._create_train_tab(), "편성 설정")
# 날씨 탭
self.tabs.addTab(self._create_weather_tab(), "날씨")
# 스타일 적용
theme = self.config.theme
if theme == 'dark':
self.tabs.setStyleSheet("""
QTabWidget::pane {
border: 1px solid #334155;
border-radius: 8px;
background-color: #1e293b;
}
QTabBar::tab {
background-color: #1e293b;
color: #94a3b8;
padding: 8px 16px;
border-top-left-radius: 6px;
border-top-right-radius: 6px;
}
QTabBar::tab:selected {
background-color: #334155;
color: #f8fafc;
}
""")
else:
self.tabs.setStyleSheet("""
QTabWidget::pane {
border: 1px solid #e2e8f0;
border-radius: 8px;
background-color: #ffffff;
}
QTabBar::tab {
background-color: #f1f5f9;
color: #64748b;
padding: 8px 16px;
border-top-left-radius: 6px;
border-top-right-radius: 6px;
}
QTabBar::tab:selected {
background-color: #ffffff;
color: #1e293b;
}
""")
self.content_layout.addWidget(self.tabs)
def _create_general_tab(self) -> QWidget:
"""일반 탭 생성"""
tab = QWidget()
layout = QVBoxLayout(tab)
layout.setContentsMargins(16, 16, 16, 16)
layout.setSpacing(16)
# 테마
themes = ["다크", "라이트"]
self.theme_combo = CustomComboBox(items=themes)
current_theme = "다크" if self.config.theme == 'dark' else "라이트"
self.theme_combo.set_selected_value(current_theme)
layout.addWidget(LabeledInput("테마", self.theme_combo))
# 자동 저장
self.auto_save_toggle = LabeledToggle(
"자동 저장",
initial_state=self.config.get('app', 'auto_save', True)
)
layout.addWidget(self.auto_save_toggle)
# 업데이트 확인
self.update_toggle = LabeledToggle(
"업데이트 자동 확인",
initial_state=self.config.get('app', 'check_updates', True)
)
layout.addWidget(self.update_toggle)
layout.addStretch()
return tab
def _create_layout_tab(self) -> QWidget:
"""레이아웃 탭 생성"""
tab = QWidget()
layout = QVBoxLayout(tab)
layout.setContentsMargins(16, 16, 16, 16)
layout.setSpacing(16)
# 섹션/Todo 비율
info = QLabel("섹션 패널과 Todo 패널의 비율을 설정합니다.\n드래그로도 조절할 수 있습니다.")
info.setFont(QFont("GmarketSans", 11))
info.setWordWrap(True)
theme = self.config.theme
info.setStyleSheet(f"color: {'#94a3b8' if theme == 'dark' else '#64748b'};")
layout.addWidget(info)
# 섹션 비율
self.section_ratio_input = CustomLineEdit(
placeholder="70"
)
self.section_ratio_input.setText(
str(int(self.config.get('layout', 'section_panel_ratio', 70)))
)
layout.addWidget(LabeledInput("섹션 패널 비율 (%)", self.section_ratio_input))
layout.addStretch()
return tab
def _create_font_tab(self) -> QWidget:
"""UI 폰트 설정 탭 생성"""
tab = QWidget()
main_layout = QVBoxLayout(tab)
main_layout.setContentsMargins(8, 8, 8, 8)
main_layout.setSpacing(8)
# 스크롤 영역
scroll = QScrollArea()
scroll.setWidgetResizable(True)
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
content = QWidget()
layout = QVBoxLayout(content)
layout.setContentsMargins(8, 8, 8, 8)
layout.setSpacing(16)
theme = self.config.theme
is_dark = theme == 'dark'
# 설명
info = QLabel("각 UI 영역별 폰트를 설정합니다.")
info.setFont(QFont("GmarketSans", 11))
info.setStyleSheet(f"color: {'#94a3b8' if is_dark else '#64748b'};")
layout.addWidget(info)
# 영역별 폰트 설정
font_areas = [
("인포바", [
("info_bar", "title", "제목"),
("info_bar", "content", "내용"),
]),
("섹션 (지시/고장/작업/기타)", [
("section", "title", "제목"),
("section", "header", "테이블 헤더"),
("section", "content", "테이블 내용"),
]),
("할일 목록", [
("todo", "title", "제목"),
("todo", "content", "내용"),
]),
("메모", [
("memo", "title", "제목"),
("memo", "content", "내용"),
]),
("일상검수", [
("daily", "title", "제목"),
("daily", "content", "내용"),
("daily", "train", "편성번호"),
]),
("상태바", [
("status", "content", "내용"),
]),
("다이얼로그", [
("dialog", "title", "제목"),
("dialog", "label", "레이블"),
("dialog", "input", "입력"),
("dialog", "button", "버튼"),
]),
]
for area_name, settings in font_areas:
group = QGroupBox(area_name)
group.setFont(QFont("GmarketSans", 12, QFont.Bold))
if is_dark:
group.setStyleSheet("""
QGroupBox {
color: #f8fafc;
border: 1px solid #334155;
border-radius: 8px;
margin-top: 12px;
padding-top: 8px;
}
QGroupBox::title {
subcontrol-origin: margin;
left: 12px;
padding: 0 6px;
}
""")
else:
group.setStyleSheet("""
QGroupBox {
color: #1e293b;
border: 1px solid #e2e8f0;
border-radius: 8px;
margin-top: 12px;
padding-top: 8px;
}
QGroupBox::title {
subcontrol-origin: margin;
left: 12px;
padding: 0 6px;
}
""")
group_layout = QVBoxLayout(group)
group_layout.setContentsMargins(12, 16, 12, 12)
group_layout.setSpacing(4)
for area, style, label in settings:
# 현재 설정값 가져오기
current = self.config.get_ui_font(area, style)
row = FontSettingRow(
label,
initial_family=current.get("family", FONT_FAMILY),
initial_size=current.get("size", 13),
initial_weight=current.get("weight", "normal"),
)
group_layout.addWidget(row)
# 저장을 위해 참조 보관
key = f"{area}_{style}"
self._font_settings[key] = row
layout.addWidget(group)
layout.addStretch()
# 초기화 버튼
reset_btn = CustomButton("폰트 설정 초기화", style_type="secondary")
reset_btn.clicked.connect(self._reset_font_settings)
layout.addWidget(reset_btn)
scroll.setWidget(content)
# 스크롤바 스타일
if is_dark:
scroll.setStyleSheet("""
QScrollArea {
border: none;
background-color: #1e293b;
}
QScrollBar:vertical {
background-color: #1e293b;
width: 10px;
border-radius: 5px;
}
QScrollBar::handle:vertical {
background-color: #475569;
border-radius: 5px;
min-height: 30px;
}
QScrollBar::handle:vertical:hover {
background-color: #64748b;
}
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {
height: 0px;
}
""")
else:
scroll.setStyleSheet("""
QScrollArea {
border: none;
background-color: #ffffff;
}
QScrollBar:vertical {
background-color: #f1f5f9;
width: 10px;
border-radius: 5px;
}
QScrollBar::handle:vertical {
background-color: #cbd5e1;
border-radius: 5px;
min-height: 30px;
}
QScrollBar::handle:vertical:hover {
background-color: #94a3b8;
}
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {
height: 0px;
}
""")
main_layout.addWidget(scroll)
return tab
def _create_train_tab(self) -> QWidget:
"""편성 설정 탭 생성"""
tab = QWidget()
main_layout = QVBoxLayout(tab)
main_layout.setContentsMargins(16, 16, 16, 16)
main_layout.setSpacing(12)
theme = self.config.theme
is_dark = theme == 'dark'
text_color = "#f8fafc" if is_dark else "#1e293b"
sub_color = "#94a3b8" if is_dark else "#64748b"
# 설명
info = QLabel("각 편성의 차량 유형을 설정합니다.\nA: 구형차량 (파란색), B: 신형차량 (주황색)")
info.setFont(QFont("GmarketSans", 11))
info.setStyleSheet(f"color: {sub_color};")
info.setWordWrap(True)
main_layout.addWidget(info)
# 일괄 설정 버튼
btn_container = QWidget()
btn_layout = QHBoxLayout(btn_container)
btn_layout.setContentsMargins(0, 0, 0, 0)
btn_layout.setSpacing(8)
all_a_btn = QPushButton("전체 A(구형)")
all_a_btn.setFont(QFont("GmarketSans", 11))
all_a_btn.setCursor(Qt.PointingHandCursor)
all_a_btn.clicked.connect(lambda: self._set_all_trains('A'))
all_a_btn.setStyleSheet(f"""
QPushButton {{
background-color: #3b82f6;
color: white;
border: none;
border-radius: 6px;
padding: 8px 16px;
}}
QPushButton:hover {{
background-color: #2563eb;
}}
""")
btn_layout.addWidget(all_a_btn)
all_b_btn = QPushButton("전체 B(신형)")
all_b_btn.setFont(QFont("GmarketSans", 11))
all_b_btn.setCursor(Qt.PointingHandCursor)
all_b_btn.clicked.connect(lambda: self._set_all_trains('B'))
all_b_btn.setStyleSheet(f"""
QPushButton {{
background-color: #f97316;
color: white;
border: none;
border-radius: 6px;
padding: 8px 16px;
}}
QPushButton:hover {{
background-color: #ea580c;
}}
""")
btn_layout.addWidget(all_b_btn)
reset_btn = QPushButton("기본값으로 초기화")
reset_btn.setFont(QFont("GmarketSans", 11))
reset_btn.setCursor(Qt.PointingHandCursor)
reset_btn.clicked.connect(self._reset_train_settings)
reset_btn.setStyleSheet(f"""
QPushButton {{
background-color: {'#334155' if is_dark else '#e2e8f0'};
color: {text_color};
border: none;
border-radius: 6px;
padding: 8px 16px;
}}
QPushButton:hover {{
background-color: {'#475569' if is_dark else '#cbd5e1'};
}}
""")
btn_layout.addWidget(reset_btn)
btn_layout.addStretch()
main_layout.addWidget(btn_container)
# 편성 테이블
self.train_table = QTableWidget()
self.train_table.setColumnCount(6)
self.train_table.setHorizontalHeaderLabels(["편성", "유형", "표시", "편성", "유형", "표시"])
self.train_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.train_table.verticalHeader().setVisible(False)
self.train_table.setSelectionMode(QTableWidget.NoSelection)
self.train_table.setEditTriggers(QTableWidget.NoEditTriggers)
# 테이블 스타일
if is_dark:
self.train_table.setStyleSheet("""
QTableWidget {
background-color: #1e293b;
color: #f8fafc;
border: 1px solid #334155;
border-radius: 8px;
gridline-color: #334155;
}
QTableWidget::item {
padding: 4px;
}
QHeaderView::section {
background-color: #334155;
color: #f8fafc;
padding: 8px;
border: none;
font-weight: bold;
}
""")
else:
self.train_table.setStyleSheet("""
QTableWidget {
background-color: #ffffff;
color: #1e293b;
border: 1px solid #e2e8f0;
border-radius: 8px;
gridline-color: #e2e8f0;
}
QTableWidget::item {
padding: 4px;
}
QHeaderView::section {
background-color: #f1f5f9;
color: #1e293b;
padding: 8px;
border: none;
font-weight: bold;
}
""")
# 테이블 데이터 채우기
self._load_train_table()
main_layout.addWidget(self.train_table, 1)
return tab
def _load_train_table(self):
"""편성 테이블 데이터 로드"""
train_config = self.config.settings.train
# 51개 편성을 2열로 표시 (왼쪽 26개, 오른쪽 25개)
rows = 26
self.train_table.setRowCount(rows)
for row in range(rows):
# 왼쪽 열 (1~26편성)
train_num = row + 1
if train_num <= 51:
self._set_train_row(row, 0, train_num, train_config)
# 오른쪽 열 (27~51편성)
train_num = row + 27
if train_num <= 51:
self._set_train_row(row, 3, train_num, train_config)
def _set_train_row(self, row: int, col_offset: int, train_num: int, train_config):
"""테이블 한 행 설정"""
key = f"train_{train_num}_type"
train_type = getattr(train_config, key, 'A')
display_num = 100 + train_num
display = f"{display_num}{train_type}"
# 편성 번호
num_item = QTableWidgetItem(str(train_num))
num_item.setTextAlignment(Qt.AlignCenter)
self.train_table.setItem(row, col_offset, num_item)
# 유형 토글 버튼
type_btn = QPushButton(train_type)
type_btn.setFont(QFont("GmarketSans", 11, QFont.Bold))
type_btn.setCursor(Qt.PointingHandCursor)
type_btn.setProperty("train_num", train_num)
type_btn.clicked.connect(lambda checked, btn=type_btn: self._toggle_train_type(btn))
self._update_train_btn_style(type_btn, train_type)
self.train_table.setCellWidget(row, col_offset + 1, type_btn)
# 표시
display_item = QTableWidgetItem(display)
display_item.setTextAlignment(Qt.AlignCenter)
if train_type == 'A':
display_item.setForeground(QColor("#3b82f6"))
else:
display_item.setForeground(QColor("#f97316"))
self.train_table.setItem(row, col_offset + 2, display_item)
def _update_train_btn_style(self, btn: QPushButton, train_type: str):
"""편성 버튼 스타일 업데이트"""
if train_type == 'A':
btn.setStyleSheet("""
QPushButton {
background-color: #3b82f6;
color: white;
border: none;
border-radius: 4px;
padding: 4px 8px;
min-width: 30px;
}
QPushButton:hover {
background-color: #2563eb;
}
""")
else:
btn.setStyleSheet("""
QPushButton {
background-color: #f97316;
color: white;
border: none;
border-radius: 4px;
padding: 4px 8px;
min-width: 30px;
}
QPushButton:hover {
background-color: #ea580c;
}
""")
def _toggle_train_type(self, btn: QPushButton):
"""편성 유형 토글"""
train_num = btn.property("train_num")
current = btn.text()
new_type = 'B' if current == 'A' else 'A'
btn.setText(new_type)
self._update_train_btn_style(btn, new_type)
# 설정 업데이트
key = f"train_{train_num}_type"
setattr(self.config.settings.train, key, new_type)
# 표시 업데이트
self._load_train_table()
def _set_all_trains(self, train_type: str):
"""모든 편성 유형 일괄 설정"""
train_config = self.config.settings.train
for i in range(1, 52):
key = f"train_{i}_type"
setattr(train_config, key, train_type)
self._load_train_table()
def _reset_train_settings(self):
"""편성 설정 초기화"""
self.config.reset_to_default('train')
self._load_train_table()
self.signals.status_message.emit("편성 설정이 초기화되었습니다.", 2000)
def _create_weather_tab(self) -> QWidget:
"""날씨 탭 생성"""
tab = QWidget()
layout = QVBoxLayout(tab)
layout.setContentsMargins(16, 16, 16, 16)
layout.setSpacing(16)
theme = self.config.theme
is_dark = theme == 'dark'
sub_color = "#94a3b8" if is_dark else "#64748b"
# 설명
info = QLabel("날씨 정보 표시 및 지역 설정을 관리합니다.")
info.setFont(QFont("GmarketSans", 11))
info.setStyleSheet(f"color: {sub_color};")
info.setWordWrap(True)
layout.addWidget(info)
# 날씨 활성화
self.weather_toggle = LabeledToggle(
"날씨 정보 표시",
initial_state=self.config.get('weather', 'enabled', True)
)
layout.addWidget(self.weather_toggle)
# API 키
self.api_key_input = CustomLineEdit(
placeholder="OpenWeatherMap API 키"
)
self.api_key_input.setText(self.config.get('weather', 'api_key', ''))
layout.addWidget(LabeledInput("API 키", self.api_key_input))
# 지역 설정
from ui.dialogs.weather_location_dialog import CITIES
self.location_combo = CustomComboBox(items=CITIES)
current_location = self.config.get('weather', 'location_name', '부산')
if current_location in CITIES:
self.location_combo.set_selected_value(current_location)
else:
self.location_combo.set_selected_value('부산')
layout.addWidget(LabeledInput("지역", self.location_combo))
# 예보 단위 설정
forecast_units = ["1시간 단위", "3시간 단위"]
self.forecast_unit_combo = CustomComboBox(items=forecast_units)
current_unit = self.config.get('weather', 'forecast_unit', '1시간 단위')
if current_unit in forecast_units:
self.forecast_unit_combo.set_selected_value(current_unit)
else:
self.forecast_unit_combo.set_selected_value('1시간 단위')
layout.addWidget(LabeledInput("예보 단위", self.forecast_unit_combo))
# 업데이트 주기
update_intervals = ["10분", "30분", "1시간", "2시간"]
self.update_interval_combo = CustomComboBox(items=update_intervals)
current_interval_sec = self.config.get('weather', 'update_interval', 1800)
current_interval_min = current_interval_sec // 60
if current_interval_min == 10:
interval_str = "10분"
elif current_interval_min == 30:
interval_str = "30분"
elif current_interval_min == 60:
interval_str = "1시간"
elif current_interval_min == 120:
interval_str = "2시간"
else:
interval_str = "30분"
self.update_interval_combo.set_selected_value(interval_str)
layout.addWidget(LabeledInput("업데이트 주기", self.update_interval_combo))
layout.addStretch()
return tab
def _reset_font_settings(self):
"""폰트 설정 초기화"""
self.config.reset_to_default('ui_font')
# UI 업데이트
for key, row in self._font_settings.items():
area, style = key.rsplit('_', 1)
current = self.config.get_ui_font(area, style)
row.set_values(
current.get("family", FONT_FAMILY),
current.get("size", 13),
current.get("weight", "normal")
)
self.signals.status_message.emit("폰트 설정이 초기화되었습니다.", 2000)
def _on_confirm(self):
"""저장 버튼 클릭"""
# 일반 설정
theme = 'dark' if self.theme_combo.currentText() == "다크" else 'light'
self.config.theme = theme
self.config.set('app', 'auto_save', self.auto_save_toggle.is_on)
self.config.set('app', 'check_updates', self.update_toggle.is_on)
# 레이아웃 설정
try:
section_ratio = int(self.section_ratio_input.text())
section_ratio = max(30, min(80, section_ratio))
self.config.set('layout', 'section_panel_ratio', section_ratio)
self.config.set('layout', 'todo_panel_ratio', 100 - section_ratio)
except ValueError:
pass
# 폰트 설정
for key, row in self._font_settings.items():
area, style = key.rsplit('_', 1)
values = row.get_values()
self.config.set_ui_font(
area, style,
family=values["family"],
size=values["size"],
weight=values["weight"]
)
# 날씨 설정
self.config.set('weather', 'enabled', self.weather_toggle.is_on)
self.config.set('weather', 'api_key', self.api_key_input.text())
# 지역 설정
selected_city = self.location_combo.currentText()
self.config.set('weather', 'location_name', selected_city)
# 지역 좌표 업데이트
from ui.dialogs.weather_location_dialog import CITY_COORDINATES
if selected_city in CITY_COORDINATES:
lat, lon = CITY_COORDINATES[selected_city]
self.config.set('weather', 'location_lat', lat)
self.config.set('weather', 'location_lon', lon)
# 예보 단위 설정
forecast_unit = self.forecast_unit_combo.currentText()
self.config.set('weather', 'forecast_unit', forecast_unit)
# 업데이트 주기 설정
interval_str = self.update_interval_combo.currentText()
if interval_str == "10분":
interval_sec = 600
elif interval_str == "30분":
interval_sec = 1800
elif interval_str == "1시간":
interval_sec = 3600
elif interval_str == "2시간":
interval_sec = 7200
else:
interval_sec = 1800
self.config.set('weather', 'update_interval', interval_sec)
# 저장
self.config.save()
# 테마 변경 시그널
self.signals.theme_changed.emit(theme)
self.signals.layout_changed.emit()
self.accept()