Files
Dispatch/Dispatch_V0.1.1/ui/ticket_selection_list.py
2026-04-29 08:18:54 +04:00

172 lines
6.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
# hub/ticket/ui/ticket_selection_list.py
"""Контейнерный список выбора Ticket на локальной GUI-библиотеке."""
from __future__ import annotations
from dataclasses import dataclass
from PySide6.QtCore import Qt, Signal
from gui.components import Label, VSpring
from gui.containers import ScrollContainer, SContainer, VContainer
@dataclass(frozen=True, slots=True)
class TicketSelectionEntry:
"""Описывает одну запись в контейнерном списке Ticket."""
entry_id: object
title: str
subtitle: str = ""
class _TicketSelectionItem(SContainer):
"""Визуальный элемент выбора записи Ticket."""
clicked = Signal(object)
activated = Signal(object)
def __init__(
self,
entry: TicketSelectionEntry,
parent=None,
):
super().__init__(
margin=0,
spacing=2,
content_fit=True,
parent=parent,
style="TICKET_LIST_ITEM",
active_style="TICKET_LIST_ITEM_SELECTED",
is_active=False,
)
self._entry = entry
self.setCursor(Qt.CursorShape.PointingHandCursor)
self._setup_ui()
@property
def entry_id(self) -> object:
return self._entry.entry_id
def set_selected(self, selected: bool) -> None:
self.style(is_active=selected)
def mousePressEvent(self, event) -> None:
if event.button() == Qt.MouseButton.LeftButton:
self.clicked.emit(self._entry.entry_id)
super().mousePressEvent(event)
def mouseDoubleClickEvent(self, event) -> None:
if event.button() == Qt.MouseButton.LeftButton:
self.clicked.emit(self._entry.entry_id)
self.activated.emit(self._entry.entry_id)
super().mouseDoubleClickEvent(event)
def _setup_ui(self) -> None:
# Body элемента списка: вертикальный блок title/subtitle внутри кликабельной записи.
body = VContainer(margin=10, spacing=2, parent=self)
body.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, True)
title_label = Label(
self._entry.title,
alignment="left",
style="TICKET_LIST_TITLE",
)
title_label.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, True)
body.add_widget(title_label)
if self._entry.subtitle:
subtitle_label = Label(
self._entry.subtitle,
alignment="left",
style="TICKET_LIST_SUBTITLE",
)
subtitle_label.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, True)
body.add_widget(subtitle_label)
class TicketSelectionList(SContainer):
"""Переиспользуемый список выбора на контейнерах и локальных компонентах."""
selection_changed = Signal(object)
item_activated = Signal(object)
def __init__(self, parent=None):
super().__init__(spacing=0, parent=parent)
self._items: dict[object, _TicketSelectionItem] = {}
self._current_entry_id: object | None = None
self._items_host: VContainer | None = None
self._setup_ui()
def set_entries(self, entries: list[TicketSelectionEntry]) -> None:
previous_entry_id = self._current_entry_id
self.clear_entries()
for entry in entries:
self._add_entry(entry)
if not self._items:
self._current_entry_id = None
self.selection_changed.emit(None)
return
target_entry_id = previous_entry_id if previous_entry_id in self._items else entries[0].entry_id
self.set_current_entry(target_entry_id)
def clear_entries(self) -> None:
if self._items_host is None:
self._items.clear()
self._current_entry_id = None
return
for item in list(self._items.values()):
self._items_host.remove_widget(item)
item.setParent(None)
self._items.clear()
self._current_entry_id = None
def current_entry_id(self) -> object | None:
return self._current_entry_id
def has_selection(self) -> bool:
return self._current_entry_id is not None
def set_current_entry(self, entry_id: object | None) -> None:
normalized_entry_id = entry_id if entry_id in self._items else None
if normalized_entry_id == self._current_entry_id:
return
self._current_entry_id = normalized_entry_id
for item_entry_id, item in self._items.items():
item.set_selected(item_entry_id == normalized_entry_id)
self.selection_changed.emit(normalized_entry_id)
def _setup_ui(self) -> None:
# Scroll-контейнер списка: внешняя прокручиваемая оболочка всех записей TicketSelectionList.
scroll = ScrollContainer(
margin=0,
content_margins=[0, 0, 0, 0],
spacing=6,
orientation="v",
vertical_scroll_bar_policy="as_needed",
horizontal_scroll_bar_policy="always_off",
style="SCROLL_CONTAINER",
parent=self,
)
# Items-host: вертикальный стек элементов списка с нижней пружиной для прилипания вверх.
self._items_host = VContainer(spacing=6, parent=scroll)
self._items_host.add_widget(VSpring())
def _add_entry(self, entry: TicketSelectionEntry) -> None:
if self._items_host is None:
return
item = _TicketSelectionItem(entry)
item.clicked.connect(self._on_item_clicked)
item.activated.connect(self._on_item_activated)
self._items[entry.entry_id] = item
self._items_host.insert_widget(len(self._items) - 1, item)
def _on_item_clicked(self, entry_id: object) -> None:
self.set_current_entry(entry_id)
def _on_item_activated(self, entry_id: object) -> None:
if entry_id != self._current_entry_id:
self.set_current_entry(entry_id)
self.item_activated.emit(entry_id)