876 lines
30 KiB
Python
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()
|