Add Dispatch_V0.1.1
This commit is contained in:
171
Dispatch_V0.1.1/ui/ticket_selection_list.py
Normal file
171
Dispatch_V0.1.1/ui/ticket_selection_list.py
Normal 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)
|
||||
Reference in New Issue
Block a user