Add Dispatch_V0.1.1

This commit is contained in:
2026-04-29 08:18:54 +04:00
commit a7ede6ded4
404 changed files with 39167 additions and 0 deletions

View File

@@ -0,0 +1,171 @@
# -*- 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)