283 lines
9.9 KiB
Python
283 lines
9.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
편성 선택 위젯 모듈
|
|
편성을 칩 형태로 표시하고 선택할 수 있는 재사용 가능한 위젯입니다.
|
|
"""
|
|
|
|
from typing import List, Dict, Any, Optional
|
|
|
|
from PySide6.QtWidgets import (
|
|
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
|
|
QScrollArea, QButtonGroup, QFrame
|
|
)
|
|
from PySide6.QtCore import Qt, Signal
|
|
from PySide6.QtGui import QFont
|
|
|
|
from ui.components.chips.choice_chip_button import ChoiceChipButton
|
|
from ui.components.flow_layout import FlowLayout
|
|
from core.config import ConfigManager
|
|
from database.common_db_manager import CommonDatabaseManager
|
|
|
|
|
|
class TrainChip(QPushButton):
|
|
"""
|
|
편성 선택 칩
|
|
|
|
편성 번호를 표시하며, 신차/구차에 따라 다른 색상을 적용합니다.
|
|
- 구차: 파란색 배경
|
|
- 신차: 주황색 배경
|
|
"""
|
|
|
|
chip_selected = Signal(int, str) # train_id, train_number
|
|
|
|
def __init__(self, train_id: int, train_number: str, is_new: bool, parent=None):
|
|
super().__init__(parent)
|
|
|
|
self.config = ConfigManager()
|
|
self.train_id = train_id
|
|
self.train_number = train_number
|
|
self.is_new = is_new
|
|
self._is_selected = False
|
|
|
|
self.setText(self.train_number)
|
|
self.setFixedSize(60, 32)
|
|
self.setCursor(Qt.PointingHandCursor)
|
|
self.setFont(QFont("GmarketSans", 10, QFont.Bold))
|
|
|
|
self.clicked.connect(self._on_clicked)
|
|
self._apply_style()
|
|
|
|
def _on_clicked(self):
|
|
"""클릭 이벤트"""
|
|
self.chip_selected.emit(self.train_id, self.train_number)
|
|
|
|
def set_selected(self, selected: bool):
|
|
"""선택 상태 설정"""
|
|
self._is_selected = selected
|
|
self._apply_style()
|
|
|
|
def _apply_style(self):
|
|
"""스타일 적용"""
|
|
# 구차: 파란색, 신차: 주황색
|
|
if not self.is_new:
|
|
base_color = "#3b82f6"
|
|
selected_border = "#1d4ed8"
|
|
else:
|
|
base_color = "#f97316"
|
|
selected_border = "#c2410c"
|
|
|
|
if self._is_selected:
|
|
self.setStyleSheet(f"""
|
|
QPushButton {{
|
|
background-color: {base_color};
|
|
color: white;
|
|
border: 3px solid {selected_border};
|
|
border-radius: 8px;
|
|
font-weight: bold;
|
|
}}
|
|
""")
|
|
else:
|
|
theme = self.config.theme
|
|
if theme == 'dark':
|
|
bg = "#334155"
|
|
text = "#94a3b8"
|
|
else:
|
|
bg = "#e2e8f0"
|
|
text = "#64748b"
|
|
|
|
self.setStyleSheet(f"""
|
|
QPushButton {{
|
|
background-color: {bg};
|
|
color: {text};
|
|
border: 2px solid transparent;
|
|
border-radius: 8px;
|
|
}}
|
|
QPushButton:hover {{
|
|
background-color: {base_color};
|
|
color: white;
|
|
}}
|
|
""")
|
|
|
|
|
|
class TrainSelectionWidget(QWidget):
|
|
"""
|
|
편성 선택 위젯
|
|
|
|
편성 목록을 로드하고 칩 형태로 표시합니다.
|
|
배속 필터링(전체/신평/노포) 기능을 제공합니다.
|
|
"""
|
|
|
|
train_selected = Signal(str) # train_number
|
|
|
|
def __init__(self, parent=None, existing_trains: List[str] = None):
|
|
super().__init__(parent)
|
|
|
|
self.config = ConfigManager()
|
|
self.db = CommonDatabaseManager()
|
|
self.existing_trains = existing_trains or []
|
|
|
|
self._selected_train_number: str = ""
|
|
self._train_chips: List[TrainChip] = []
|
|
|
|
self._setup_ui()
|
|
self._load_trains(depot_filter="신평") # 기본값
|
|
|
|
def _setup_ui(self):
|
|
"""UI 설정"""
|
|
layout = QVBoxLayout(self)
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
layout.setSpacing(8)
|
|
|
|
theme = self.config.theme
|
|
text_color = "#f8fafc" if theme == 'dark' else "#1e293b"
|
|
border_color = "#334155" if theme == 'dark' else "#e2e8f0"
|
|
|
|
# 헤더 (라벨 + 필터)
|
|
header = QWidget()
|
|
header_layout = QHBoxLayout(header)
|
|
header_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
# 배속 필터
|
|
self.depot_group = QButtonGroup()
|
|
self.depot_group.setExclusive(True)
|
|
self.selected_depot = "신평"
|
|
|
|
depot_container = QWidget()
|
|
depot_layout = QHBoxLayout(depot_container)
|
|
depot_layout.setContentsMargins(0, 0, 0, 0)
|
|
depot_layout.setSpacing(4)
|
|
|
|
def create_depot_chip(text: str, key: str):
|
|
is_selected = (key == self.selected_depot)
|
|
bg_color = "#3b82f6" if is_selected else "#404040"
|
|
|
|
chip = ChoiceChipButton(text=text, key=key, bg=bg_color)
|
|
chip.setFixedHeight(28)
|
|
chip.setFont(QFont("GmarketSans", 10, QFont.Bold))
|
|
self.depot_group.addButton(chip)
|
|
depot_layout.addWidget(chip)
|
|
|
|
if is_selected:
|
|
chip.setChecked(True)
|
|
|
|
def make_handler(depot_key: str):
|
|
def handler():
|
|
self.selected_depot = depot_key
|
|
for btn in self.depot_group.buttons():
|
|
if isinstance(btn, ChoiceChipButton):
|
|
if btn.key == depot_key:
|
|
btn.set_bg("#3b82f6")
|
|
else:
|
|
btn.set_bg("#404040")
|
|
self._on_depot_filter_changed(depot_key)
|
|
return handler
|
|
|
|
chip.clicked_key.connect(make_handler(key))
|
|
return chip
|
|
|
|
create_depot_chip("전체", "전체")
|
|
create_depot_chip("신평", "신평")
|
|
create_depot_chip("노포", "노포")
|
|
|
|
header_layout.addWidget(depot_container)
|
|
header_layout.addStretch()
|
|
|
|
layout.addWidget(header)
|
|
|
|
# 편성 칩 컨테이너 (스크롤)
|
|
self.train_scroll = QScrollArea()
|
|
self.train_scroll.setWidgetResizable(True)
|
|
self.train_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
|
self.train_scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
|
self.train_scroll.setMinimumHeight(160)
|
|
self.train_scroll.setMaximumHeight(180)
|
|
|
|
scroll_bg = "#1e293b" if theme == 'dark' else "#f8fafc"
|
|
self.train_scroll.setStyleSheet(f"""
|
|
QScrollArea {{
|
|
background-color: {scroll_bg};
|
|
border: 1px solid {border_color};
|
|
border-radius: 8px;
|
|
}}
|
|
QScrollArea > QWidget > QWidget {{
|
|
background-color: {scroll_bg};
|
|
}}
|
|
""")
|
|
|
|
self.train_container = QWidget()
|
|
self.train_flow_layout = FlowLayout(margin=8, h_spacing=6, v_spacing=6)
|
|
self.train_container.setLayout(self.train_flow_layout)
|
|
self.train_scroll.setWidget(self.train_container)
|
|
|
|
layout.addWidget(self.train_scroll)
|
|
|
|
def _load_trains(self, depot_filter: str = None):
|
|
"""DB에서 편성 목록 로드"""
|
|
query = "SELECT * FROM train_formations WHERE 1=1"
|
|
params = []
|
|
|
|
if depot_filter and depot_filter != "전체":
|
|
query += " AND depot = ?"
|
|
params.append(depot_filter)
|
|
|
|
query += " ORDER BY train_number"
|
|
|
|
trains = self.db.fetch_all(query, tuple(params) if params else None)
|
|
self._create_train_chips(trains)
|
|
|
|
def _create_train_chips(self, trains: List[Dict[str, Any]]):
|
|
"""편성 칩 생성"""
|
|
for chip in self._train_chips:
|
|
chip.deleteLater()
|
|
self._train_chips.clear()
|
|
|
|
for train in trains:
|
|
train_id = train.get('id')
|
|
train_number = train.get('train_number', '')
|
|
is_new = bool(train.get('is_new_train', True))
|
|
|
|
chip = TrainChip(train_id, train_number, is_new)
|
|
chip.chip_selected.connect(self._on_train_selected)
|
|
|
|
if train_number in self.existing_trains and train_number != self._selected_train_number:
|
|
chip.setEnabled(False)
|
|
chip.setToolTip("이미 다른 슬롯에 등록된 편성입니다.")
|
|
chip.setStyleSheet(f"""
|
|
QPushButton {{
|
|
background-color: {'#404040' if self.config.theme == 'dark' else '#e2e8f0'};
|
|
color: {'#737373' if self.config.theme == 'dark' else '#94a3b8'};
|
|
border: 1px solid transparent;
|
|
border-radius: 8px;
|
|
}}
|
|
""")
|
|
|
|
if self._selected_train_number == train_number:
|
|
chip.set_selected(True)
|
|
|
|
self._train_chips.append(chip)
|
|
self.train_flow_layout.addWidget(chip)
|
|
|
|
self.train_container.updateGeometry()
|
|
|
|
def _on_depot_filter_changed(self, depot: str):
|
|
"""배속 필터 변경"""
|
|
self._load_trains(depot_filter=depot)
|
|
|
|
def _on_train_selected(self, train_id: int, train_number: str):
|
|
"""편성 선택"""
|
|
for chip in self._train_chips:
|
|
chip.set_selected(chip.train_number == train_number)
|
|
|
|
self._selected_train_number = train_number
|
|
self.train_selected.emit(train_number)
|
|
|
|
def set_selected_train(self, train_number: str):
|
|
"""선택된 편성 설정"""
|
|
self._selected_train_number = train_number
|
|
# 현재 로드된 칩들 중에서 찾아서 선택 상태 업데이트
|
|
for chip in self._train_chips:
|
|
chip.set_selected(chip.train_number == train_number)
|
|
|
|
def get_selected_train(self) -> str:
|
|
"""선택된 편성 반환"""
|
|
return self._selected_train_number |