Skip to content
Snippets Groups Projects
scheduler_item.py 9.65 KiB
Newer Older
  • Learn to ignore specific revisions
  • Andreas Bolin's avatar
    Andreas Bolin committed
    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    """B-ASIC Scheduler-gui Graphics Graph Item Module.
    
    
    Contains the scheduler-gui SchedulerItem class for drawing and
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    maintain a component in a graph.
    """
    from collections import defaultdict
    from math import floor
    
    from pprint import pprint
    
    Oscar Gustafsson's avatar
    Oscar Gustafsson committed
    from typing import Dict, List, Optional, Set, cast
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
    # QGraphics and QPainter imports
    from qtpy.QtWidgets import QGraphicsItem, QGraphicsItemGroup
    
    # B-ASIC
    
    Oscar Gustafsson's avatar
    Oscar Gustafsson committed
    from b_asic.operation import Operation
    from b_asic.port import InputPort
    
    from b_asic.schedule import Schedule
    
    from b_asic.scheduler_gui._preferences import OPERATION_GAP, OPERATION_HEIGHT
    
    from b_asic.scheduler_gui.axes_item import AxesItem
    from b_asic.scheduler_gui.operation_item import OperationItem
    from b_asic.scheduler_gui.scheduler_event import SchedulerEvent
    from b_asic.scheduler_gui.signal_item import SignalItem
    
    class SchedulerItem(SchedulerEvent, QGraphicsItemGroup):  # PySide2 / PyQt5
    
    Oscar Gustafsson's avatar
    Oscar Gustafsson committed
        """
        A class to represent a graph in a QGraphicsScene. This class is a
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        subclass of QGraphicsItemGroup and contains the objects, axes from
    
        AxesItem, as well as components from OperationItem. It
        also inherits from SchedulerEvent, which acts as a filter for events
    
    Oscar Gustafsson's avatar
    Oscar Gustafsson committed
        to OperationItem objects.
    
    
        Parameters
        ==========
    
        schedule : Schedule
            The Schedule to draw.
    
        parent : QGraphicsItem, optional
            The parent. Passed to the constructor of QGraphicsItemGroup
    
    Oscar Gustafsson's avatar
    Oscar Gustafsson committed
        """
    
    
    Oscar Gustafsson's avatar
    Oscar Gustafsson committed
        _axes: Optional[AxesItem]
    
        _operation_items: Dict[str, OperationItem]
    
    Oscar Gustafsson's avatar
    Oscar Gustafsson committed
        _x_axis_indent: float
        _event_items: List[QGraphicsItem]
    
        _signal_dict: Dict[OperationItem, Set[SignalItem]]
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
    
    Oscar Gustafsson's avatar
    Oscar Gustafsson committed
        def __init__(
            self, schedule: Schedule, parent: Optional[QGraphicsItem] = None
        ):
    
            """Constructs a SchedulerItem. *parent* is passed to QGraphicsItemGroup's constructor.
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            # QGraphicsItemGroup.__init__(self, self)
    
            # SchedulerEvent.__init__(self)
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            super().__init__(parent=parent)
            # if isinstance(parent, QGraphicsItem):
            #     super().__init__(parent=parent)
            # else:
            #     super().__init__(parent=self)
            self._schedule = schedule
            self._axes = None
    
            self._operation_items = {}
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self._x_axis_indent = 0.2
            self._event_items = []
            self._signal_dict = defaultdict(set)
            self._make_graph()
    
        def clear(self) -> None:
    
            """
            Sets all children's parent to 'None' and delete the children objects.
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self._event_items = []
            for item in self.childItems():
                item.setParentItem(None)
                del item
    
    
        def is_component_valid_pos(self, item: OperationItem, pos: float) -> bool:
    
            """
            Takes in a component position and returns true if the component's new
            position is valid, false otherwise.
    
            Parameters
            ==========
    
            item : OperationItem
                The component.
    
            pos : float
                The x-position to check.
            """
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            # TODO: implement
    
    Oscar Gustafsson's avatar
    Oscar Gustafsson committed
            assert self.schedule is not None, "No schedule installed."
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            end_time = item.end_time
            new_start_time = floor(pos) - floor(self._x_axis_indent)
    
            slacks = self.schedule.slacks(item.graph_id)
            op_start_time = self.schedule.start_time_of_operation(item.graph_id)
    
    Oscar Gustafsson's avatar
    Oscar Gustafsson committed
            if not -slacks[0] <= new_start_time - op_start_time <= slacks[1]:
    
    Oscar Gustafsson's avatar
    Oscar Gustafsson committed
                # Cannot move due to dependencies
    
    Andreas Bolin's avatar
    Andreas Bolin committed
                return False
    
    Oscar Gustafsson's avatar
    Oscar Gustafsson committed
            if self.schedule.cyclic:
                if new_start_time < -1:
                    # Moving one position before left edge => wrap
                    return False
                if new_start_time > self.schedule.schedule_time + 1:
                    # Moving one position after schedule_time => wrap
                    return False
            else:
                if pos < 0:
                    return False
                if new_start_time + end_time > self.schedule.schedule_time:
                    return False
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
            return True
    
    
        def _redraw_all_lines(self) -> None:
            """Redraw all lines in schedule."""
    
            s = set()
            for signals in self._signal_dict.values():
                s.update(signals)
            for signal in s:
                signal.update_path()
    
    
        def _redraw_lines(self, item: OperationItem) -> None:
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            """Update lines connected to *item*."""
            for signal in self._signal_dict[item]:
                signal.update_path()
    
    
        def set_item_active(self, item: OperationItem) -> None:
            """
            Set an item as active, i.e., draw it and connecting signals in special colors.
    
            Parameters
            ----------
            item : OperationItem
                The item to set as active.
    
            """
    
            item.set_active()
            for signal in self._signal_dict[item]:
                signal.set_active()
    
    
        def set_item_inactive(self, item: OperationItem) -> None:
            """
            Set an item as inactive, i.e., draw it and connecting signals in standard colors.
    
            Parameters
            ----------
            item : OperationItem
                The item to set as active.
    
            """
    
            item.set_inactive()
            for signal in self._signal_dict[item]:
                signal.set_inactive()
    
    
        def set_new_starttime(self, item: OperationItem) -> None:
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            """Set new starttime for *item*."""
            pos = item.x()
    
            op_start_time = self.schedule.start_time_of_operation(item.graph_id)
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            new_start_time = floor(pos) - floor(self._x_axis_indent)
            move_time = new_start_time - op_start_time
            if move_time:
    
                self.schedule.move_operation(item.graph_id, move_time)
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
        def is_valid_delta_time(self, delta_time: int) -> bool:
    
            """
            Takes in a delta time and returns True if the schedule time can be changed by *delta_time*. False otherwise.
            """
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            # TODO: implement
            # item = self.scene().mouseGrabberItem()
    
    Oscar Gustafsson's avatar
    Oscar Gustafsson committed
            assert self.schedule is not None, "No schedule installed."
            return (
                self.schedule.schedule_time + delta_time
                >= self.schedule.get_max_end_time()
            )
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
        def set_schedule_time(self, delta_time: int) -> None:
    
            """Change the schedule time by *delta_time* and redraw the graph."""
    
    Oscar Gustafsson's avatar
    Oscar Gustafsson committed
            if self._axes is None:
                raise RuntimeError("No AxesItem!")
    
    Oscar Gustafsson's avatar
    Oscar Gustafsson committed
            assert self.schedule is not None, "No schedule installed."
            self.schedule.set_schedule_time(
                self.schedule.schedule_time + delta_time
            )
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self._axes.set_width(self._axes.width + delta_time)
    
    Oscar Gustafsson's avatar
    Oscar Gustafsson committed
            # Redraw all lines
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
        @property
        def schedule(self) -> Schedule:
            """The schedule."""
            return self._schedule
    
        @property
    
    Oscar Gustafsson's avatar
    Oscar Gustafsson committed
        def axes(self) -> Optional[AxesItem]:
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            return self._axes
    
        @property
    
        def components(self) -> List[OperationItem]:
    
            return list(component for component in self._operation_items.values())
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
        @property
        def event_items(self) -> List[QGraphicsItem]:
    
            """Return a list of objects that receives events."""
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            return self._event_items
    
    
        def _set_position(self, graph_id) -> None:
            op_item = self._operation_items[graph_id]
            op_item.setPos(
                self._x_axis_indent + self.schedule.start_times[graph_id],
    
                self.schedule._get_y_position(
                    graph_id, OPERATION_HEIGHT, OPERATION_GAP
                ),
    
            )
    
        def _redraw_from_start(self) -> None:
            self.schedule._reset_y_locations()
            for graph_id in {
                k: v
                for k, v in sorted(
                    self.schedule.start_times.items(), key=lambda item: item[1]
    
    Oscar Gustafsson's avatar
    Oscar Gustafsson committed
                )
    
            }:
                self._set_position(graph_id)
            self._redraw_all_lines()
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
    
        def _update_axes(self, build=False):
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            # build axes
            schedule_time = self.schedule.schedule_time
    
                self.schedule._y_locations, key=self.schedule._y_locations.get
    
    Oscar Gustafsson's avatar
    Oscar Gustafsson committed
            )
    
            yposmin = self.schedule._get_y_position(
                max_pos_graph_id, OPERATION_HEIGHT, OPERATION_GAP
            )
    
            if self._axes is None or build:
                self._axes = AxesItem(schedule_time, yposmin + 0.5)
                self._event_items += self._axes.event_items
            else:
                self._axes.set_height(yposmin + 0.5)
    
            self._axes.setPos(0, yposmin + OPERATION_HEIGHT + OPERATION_GAP)
    
    
        def _make_graph(self) -> None:
            """Makes a new graph out of the stored attributes."""
            # build components
            for graph_id in self.schedule.start_times.keys():
                operation = cast(Operation, self.schedule.sfg.find_by_id(graph_id))
    
                component = OperationItem(
                    operation, height=OPERATION_HEIGHT, parent=self
                )
    
                self._operation_items[graph_id] = component
                self._set_position(graph_id)
                self._event_items += component.event_items
    
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            # self._axes.width = schedule_time
    
            # add axes and components
    
            self._update_axes(build=True)
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self.addToGroup(self._axes)
    
            for component in self.components:
    
    Andreas Bolin's avatar
    Andreas Bolin committed
                self.addToGroup(component)
    
            # add signals
    
            for component in self.components:
    
    Andreas Bolin's avatar
    Andreas Bolin committed
                for output_port in component.operation.outputs:
                    for signal in output_port.signals:
    
    Oscar Gustafsson's avatar
    Oscar Gustafsson committed
                        destination = cast(InputPort, signal.destination)
    
                        dest_component = self._operation_items[
                            destination.operation.graph_id
                        ]
    
                        gui_signal = SignalItem(
    
    Oscar Gustafsson's avatar
    Oscar Gustafsson committed
                            component, dest_component, signal, parent=self
                        )
    
    Andreas Bolin's avatar
    Andreas Bolin committed
                        self.addToGroup(gui_signal)
                        self._signal_dict[component].add(gui_signal)
                        self._signal_dict[dest_component].add(gui_signal)
    
    
    pprint(SchedulerItem.__mro__)