# -*- 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