handOver2/ui/components/train_selection.py

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