"""Contains layouts for queue and queue header."""
from typing import Any
from PyQt5 import QtWidgets
from PyQt5.QtCore import QAbstractAnimation, Qt, QVariant, QVariantAnimation
from ..boilerplate.observable import Event, EventType
from ..models import models
from .common import get_icon, setup_animation
from .task import Task
__all__ = [
"QueueHeader",
"Queue",
]
[docs]class Queue(QtWidgets.QFrame):
"""Vertical container that stores tasks organized in a queue."""
[docs] def __init__(self, queue_model: models.Queue, *args: Any, **kwargs: Any):
"""Initialize queue.
Args:
queue_model: Observable model for tracking property changes.
"""
super().__init__(*args, **kwargs)
self.vertical_layout = QtWidgets.QVBoxLayout(self)
self.model = queue_model
self.header = QueueHeader(queue_model, self)
self.header.add_task.clicked.connect(self.on_task_addition)
self.tasks_frame = QtWidgets.QFrame(self)
size_policy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding
)
self.tasks_frame.setSizePolicy(size_policy)
self.vertical_layout.addWidget(self.header, 0, Qt.AlignTop)
self.vertical_layout.addWidget(self.tasks_frame)
self.vertical_task_layout = QtWidgets.QVBoxLayout(self.tasks_frame)
self.vertical_task_layout.setAlignment(Qt.AlignTop)
self.vertical_task_layout.addStretch(0)
self.hiding_animation = QVariantAnimation(self)
setup_animation(self.hiding_animation, 750, 1.0, 0.0, self.set_height)
self.header.collapse_queue.clicked.connect(self.toggle_collapse_queue)
self.hiding_animation.finished.connect(self.reset_size_constraints)
self.model.tasks.subscribe(
self.queue_add_task_observer,
event_types=[EventType.ADD],
)
self.model.tasks.subscribe(
self.queue_change_task_observer,
event_types=[EventType.ADD, EventType.CHANGE],
order=10, # run after other observers
)
self.model.tasks.subscribe(
self.queue_delete_task_observer,
event_types=[EventType.DISCARD],
)
[docs] def toggle_collapse_queue(self) -> None:
"""React to collapse_queue button click by hiding/showing tasks_frame."""
if self.tasks_frame.height() == 0: # is hidden
self.hiding_animation.setDirection(QAbstractAnimation.Backward)
self.hiding_animation.start()
self.tasks_frame.setEnabled(True)
self.header.collapse_queue.setIcon(get_icon("queue"))
else:
self.hiding_animation.setDirection(QAbstractAnimation.Forward)
self.hiding_animation.start()
self.tasks_frame.setEnabled(False)
self.header.collapse_queue.setIcon(get_icon("queue_closed"))
[docs] def set_height(self, val: QVariant) -> None:
"""Change the height of tasks_frame on collapsing.
Args:
val: Multiplier in range [0,1] for height-to-be-set.
"""
height = int(val * self.tasks_frame.minimumSizeHint().height())
self.tasks_frame.setFixedHeight(height)
[docs] def reset_size_constraints(self):
"""Reset fixed height for tasks_frame widget after un-collapsing."""
if self.hiding_animation.direction() == QAbstractAnimation.Backward:
self.tasks_frame.setMaximumSize(
QtWidgets.QWIDGETSIZE_MAX,
QtWidgets.QWIDGETSIZE_MAX,
)
self.tasks_frame.setMinimumSize(0, 0)
[docs] def on_task_addition(self) -> None:
"""React to add_task button click by adding new task with animation."""
if not self.tasks_frame.isEnabled():
return
self.model.tasks.add(models.Task())
[docs] def queue_add_task_observer(self, event: Event) -> None:
"""Add new task to GUI on model change.
Args:
event: An incoming event for changed property.
"""
task = Task(event.get_nested_object(), self.tasks_frame)
task.setFixedHeight(0)
self.vertical_task_layout.insertWidget(0, task)
task.adding_animation.start()
[docs] def queue_change_task_observer(self, event: Event) -> None:
"""Reorder currently existing tasks on model change.
Args:
event: An incoming event for changed property.
"""
del event
current_tasks = []
while True:
item = self.vertical_task_layout.takeAt(0)
item = item if item is None else item.widget()
if item is None:
break
current_tasks.append(item)
current_tasks = sorted(current_tasks, key=Task.queue_order_key, reverse=True)
for task in current_tasks:
self.vertical_task_layout.insertWidget(0, task)
[docs] def queue_delete_task_observer(self, event: Event) -> None:
"""Delete a task on model change.
Args:
event: An incoming event for changed property.
"""
attr_name = event.attr_name
for widget in self.tasks_frame.findChildren(Task):
if widget.model.uuid == attr_name:
widget.start_fading_out_task()
break