Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • da/B-ASIC
  • lukja239/B-ASIC
  • robal695/B-ASIC
3 results
Show changes
Commits on Source (28)
Showing
with 1043 additions and 427 deletions
...@@ -23,6 +23,7 @@ The following packages are required in order to build the library: ...@@ -23,6 +23,7 @@ The following packages are required in order to build the library:
- [Matplotlib](https://matplotlib.org/) - [Matplotlib](https://matplotlib.org/)
- [NumPy](https://numpy.org/) - [NumPy](https://numpy.org/)
- [QtPy](https://github.com/spyder-ide/qtpy) - [QtPy](https://github.com/spyder-ide/qtpy)
- [setuptools_scm](https://github.com/pypa/setuptools_scm/)
- Qt 5 or 6, with Python bindings, one of: - Qt 5 or 6, with Python bindings, one of:
- pyside2 - pyside2
- pyqt5 - pyqt5
...@@ -52,6 +53,8 @@ To generate the documentation, the following additional packages are required: ...@@ -52,6 +53,8 @@ To generate the documentation, the following additional packages are required:
- [Sphinx](https://www.sphinx-doc.org/) - [Sphinx](https://www.sphinx-doc.org/)
- [Furo](https://pradyunsg.me/furo/) - [Furo](https://pradyunsg.me/furo/)
- [numpydoc](https://numpydoc.readthedocs.io/) - [numpydoc](https://numpydoc.readthedocs.io/)
- [Sphinx-Gallery](https://sphinx-gallery.github.io/)
- [SciPy](https://scipy.org/)
### Using CMake directly ### Using CMake directly
......
...@@ -28,7 +28,15 @@ class DragButton(QPushButton): ...@@ -28,7 +28,15 @@ class DragButton(QPushButton):
""" """
Drag button class. Drag button class.
This class creates a drag button which can be clicked, dragged and dropped. This class creates a button which can be clicked, dragged and dropped.
Parameters
----------
name
operation
is_show_name
window
parent
""" """
connectionRequested = Signal(QPushButton) connectionRequested = Signal(QPushButton)
......
"""
B-ASIC port button module.
"""
from qtpy.QtCore import Qt, Signal from qtpy.QtCore import Qt, Signal
from qtpy.QtWidgets import QMenu, QPushButton from qtpy.QtWidgets import QMenu, QPushButton
class PortButton(QPushButton): class PortButton(QPushButton):
"""
A button corresponding to a port.
Parameters
----------
name
operation
port
window
parent
"""
connectionRequested = Signal(QPushButton) connectionRequested = Signal(QPushButton)
moved = Signal() moved = Signal()
......
"""
B-ASIC select SFG window.
"""
from qtpy.QtCore import Qt, Signal from qtpy.QtCore import Qt, Signal
from qtpy.QtWidgets import QComboBox, QDialog, QPushButton, QVBoxLayout from qtpy.QtWidgets import QComboBox, QDialog, QPushButton, QVBoxLayout
......
"""
B-ASIC window to show precedence graph.
"""
from qtpy.QtCore import Qt, Signal from qtpy.QtCore import Qt, Signal
from qtpy.QtWidgets import ( from qtpy.QtWidgets import (
QCheckBox, QCheckBox,
......
"""
B-ASIC window to simulate an SFG.
"""
from matplotlib.backends.backend_qt5agg import ( from matplotlib.backends.backend_qt5agg import (
FigureCanvasQTAgg as FigureCanvas, FigureCanvasQTAgg as FigureCanvas,
) )
......
...@@ -3,3 +3,9 @@ EXECUTION_TIME_COLOR = (255, 100, 66, 200) ...@@ -3,3 +3,9 @@ EXECUTION_TIME_COLOR = (255, 100, 66, 200)
SIGNAL_COLOR = (0, 0, 0) SIGNAL_COLOR = (0, 0, 0)
SIGNAL_LINEWIDTH = 1.0 SIGNAL_LINEWIDTH = 1.0
OPERATION_GAP = 0.5
SCHEDULE_OFFSET = 0.2
SPLINE_OFFSET = 0.2
...@@ -294,6 +294,10 @@ class Multiplication(AbstractOperation): ...@@ -294,6 +294,10 @@ class Multiplication(AbstractOperation):
Operation execution time (time units before operator can be Operation execution time (time units before operator can be
reused). reused).
See also
========
ConstantMultiplication
""" """
def __init__( def __init__(
...@@ -332,6 +336,9 @@ class Division(AbstractOperation): ...@@ -332,6 +336,9 @@ class Division(AbstractOperation):
.. math:: y = \frac{x_0}{x_1} .. math:: y = \frac{x_0}{x_1}
See also
========
Reciprocal
""" """
def __init__( def __init__(
...@@ -371,6 +378,10 @@ class Min(AbstractOperation): ...@@ -371,6 +378,10 @@ class Min(AbstractOperation):
.. math:: y = \min\{x_0 , x_1\} .. math:: y = \min\{x_0 , x_1\}
.. note:: Only real-valued numbers are supported. .. note:: Only real-valued numbers are supported.
See also
========
Max
""" """
def __init__( def __init__(
...@@ -414,6 +425,10 @@ class Max(AbstractOperation): ...@@ -414,6 +425,10 @@ class Max(AbstractOperation):
.. math:: y = \max\{x_0 , x_1\} .. math:: y = \max\{x_0 , x_1\}
.. note:: Only real-valued numbers are supported. .. note:: Only real-valued numbers are supported.
See also
========
Min
""" """
def __init__( def __init__(
...@@ -614,8 +629,8 @@ class Butterfly(AbstractOperation): ...@@ -614,8 +629,8 @@ class Butterfly(AbstractOperation):
.. math:: .. math::
\begin{eqnarray} \begin{eqnarray}
y_0 = x_0 + x_1\\ y_0 & = & x_0 + x_1\\
y_1 = x_0 - x_1 y_1 & = & x_0 - x_1
\end{eqnarray} \end{eqnarray}
""" """
...@@ -692,8 +707,8 @@ class SymmetricTwoportAdaptor(AbstractOperation): ...@@ -692,8 +707,8 @@ class SymmetricTwoportAdaptor(AbstractOperation):
.. math:: .. math::
\begin{eqnarray} \begin{eqnarray}
y_0 = x_1 + \text{value}\times\left(x_1 - x_0\right)\\ y_0 & = & x_1 + \text{value}\times\left(x_1 - x_0\right)\\
y_1 = x_0 + \text{value}\times\left(x_1 - x_0\right) y_1 & = & x_0 + \text{value}\times\left(x_1 - x_0\right)
\end{eqnarray} \end{eqnarray}
""" """
...@@ -707,7 +722,7 @@ class SymmetricTwoportAdaptor(AbstractOperation): ...@@ -707,7 +722,7 @@ class SymmetricTwoportAdaptor(AbstractOperation):
latency_offsets: Optional[Dict[str, int]] = None, latency_offsets: Optional[Dict[str, int]] = None,
execution_time: Optional[int] = None, execution_time: Optional[int] = None,
): ):
"""Construct a Butterfly operation.""" """Construct a SymmetricTwoportAdaptor operation."""
super().__init__( super().__init__(
input_count=2, input_count=2,
output_count=2, output_count=2,
...@@ -736,3 +751,43 @@ class SymmetricTwoportAdaptor(AbstractOperation): ...@@ -736,3 +751,43 @@ class SymmetricTwoportAdaptor(AbstractOperation):
def value(self, value: Number) -> None: def value(self, value: Number) -> None:
"""Set the constant value of this operation.""" """Set the constant value of this operation."""
self.set_param("value", value) self.set_param("value", value)
class Reciprocal(AbstractOperation):
r"""
Reciprocal operation.
Gives the reciprocal of its input.
.. math:: y = \frac{1}{x}
See also
========
Division
"""
def __init__(
self,
src0: Optional[SignalSourceProvider] = None,
name: Name = Name(""),
latency: Optional[int] = None,
latency_offsets: Optional[Dict[str, int]] = None,
execution_time: Optional[int] = None,
):
"""Construct an Reciprocal operation."""
super().__init__(
input_count=1,
output_count=1,
name=Name(name),
input_sources=[src0],
latency=latency,
latency_offsets=latency_offsets,
execution_time=execution_time,
)
@classmethod
def type_name(cls) -> TypeName:
return TypeName("rec")
def evaluate(self, a):
return 1 / a
...@@ -40,6 +40,7 @@ if TYPE_CHECKING: ...@@ -40,6 +40,7 @@ if TYPE_CHECKING:
ConstantMultiplication, ConstantMultiplication,
Division, Division,
Multiplication, Multiplication,
Reciprocal,
Subtraction, Subtraction,
) )
from b_asic.signal_flow_graph import SFG from b_asic.signal_flow_graph import SFG
...@@ -135,7 +136,7 @@ class Operation(GraphComponent, SignalSourceProvider): ...@@ -135,7 +136,7 @@ class Operation(GraphComponent, SignalSourceProvider):
@abstractmethod @abstractmethod
def __rtruediv__( def __rtruediv__(
self, src: Union[SignalSourceProvider, Number] self, src: Union[SignalSourceProvider, Number]
) -> "Division": ) -> Union["Division", "Reciprocal"]:
""" """
Overloads the division operator to make it return a new Division operation Overloads the division operator to make it return a new Division operation
object that is connected to the self and other objects. object that is connected to the self and other objects.
...@@ -240,15 +241,27 @@ class Operation(GraphComponent, SignalSourceProvider): ...@@ -240,15 +241,27 @@ class Operation(GraphComponent, SignalSourceProvider):
) -> Number: ) -> Number:
""" """
Evaluate the output at the given index of this operation with the given input values. Evaluate the output at the given index of this operation with the given input values.
The *results* parameter will be used to store any results (including intermediate results)
for caching. Parameters
The *delays* parameter will be used to get the current value of any intermediate delays ----------
that are encountered, and be updated with their new values. index : int
The *prefix* parameter will be used as a prefix for the key string when storing results/delays. Which output to return the value for.
The *bits_override* parameter specifies a word length override when truncating inputs input_values : array of float or complex
which ignores the word length specified by the input signal. The input values.
The *truncate* parameter specifies whether input truncation should be enabled in the first results : MutableResultMap. optional
place. If set to False, input values will be used directly without any bit truncation. Used to store any results (including intermediate results)
for caching.
delays : MutableDelayMap. optional
Used to get the current value of any intermediate delay elements
that are encountered, and be updated with their new values.
prefix : str, optional
Used as a prefix for the key string when storing results/delays.
bits_override ; int, optional
Specifies a word length override when truncating inputs
which ignores the word length specified by the input signal.
truncate : bool, default: True
Specifies whether input truncation should be enabled in the first
place. If set to False, input values will be used directly without any bit truncation.
See also See also
======== ========
...@@ -263,7 +276,10 @@ class Operation(GraphComponent, SignalSourceProvider): ...@@ -263,7 +276,10 @@ class Operation(GraphComponent, SignalSourceProvider):
) -> Sequence[Optional[Number]]: ) -> Sequence[Optional[Number]]:
""" """
Get all current outputs of this operation, if available. Get all current outputs of this operation, if available.
See current_output for more information.
See also
========
current_output
""" """
raise NotImplementedError raise NotImplementedError
...@@ -372,7 +388,7 @@ class Operation(GraphComponent, SignalSourceProvider): ...@@ -372,7 +388,7 @@ class Operation(GraphComponent, SignalSourceProvider):
self, self,
) -> Tuple[List[List[float]], List[List[float]]]: ) -> Tuple[List[List[float]], List[List[float]]]:
""" """
Get a tuple constaining coordinates for the two polygons outlining Return a tuple containing coordinates for the two polygons outlining
the latency and execution time of the operation. the latency and execution time of the operation.
The polygons are corresponding to a start time of 0 and are of height 1. The polygons are corresponding to a start time of 0 and are of height 1.
""" """
...@@ -383,7 +399,7 @@ class Operation(GraphComponent, SignalSourceProvider): ...@@ -383,7 +399,7 @@ class Operation(GraphComponent, SignalSourceProvider):
self, self,
) -> Tuple[List[List[float]], List[List[float]]]: ) -> Tuple[List[List[float]], List[List[float]]]:
""" """
Get a tuple constaining coordinates for inputs and outputs, respectively. Return a tuple containing coordinates for inputs and outputs, respectively.
These maps to the polygons and are corresponding to a start time of 0 These maps to the polygons and are corresponding to a start time of 0
and height 1. and height 1.
""" """
...@@ -468,7 +484,11 @@ class AbstractOperation(Operation, AbstractGraphComponent): ...@@ -468,7 +484,11 @@ class AbstractOperation(Operation, AbstractGraphComponent):
) )
for i, src in enumerate(input_sources): for i, src in enumerate(input_sources):
if src is not None: if src is not None:
self._input_ports[i].connect(src.source) if isinstance(src, Signal):
# Already existing signal
src.set_destination(self._input_ports[i])
else:
self._input_ports[i].connect(src.source)
# Set specific latency_offsets # Set specific latency_offsets
if latency_offsets is not None: if latency_offsets is not None:
...@@ -481,9 +501,9 @@ class AbstractOperation(Operation, AbstractGraphComponent): ...@@ -481,9 +501,9 @@ class AbstractOperation(Operation, AbstractGraphComponent):
for inp in self.inputs: for inp in self.inputs:
if inp.latency_offset is None: if inp.latency_offset is None:
inp.latency_offset = 0 inp.latency_offset = 0
for outp in self.outputs: for output in self.outputs:
if outp.latency_offset is None: if output.latency_offset is None:
outp.latency_offset = latency output.latency_offset = latency
self._execution_time = execution_time self._execution_time = execution_time
...@@ -573,13 +593,16 @@ class AbstractOperation(Operation, AbstractGraphComponent): ...@@ -573,13 +593,16 @@ class AbstractOperation(Operation, AbstractGraphComponent):
def __rtruediv__( def __rtruediv__(
self, src: Union[SignalSourceProvider, Number] self, src: Union[SignalSourceProvider, Number]
) -> "Division": ) -> Union["Division", "Reciprocal"]:
# Import here to avoid circular imports. # Import here to avoid circular imports.
from b_asic.core_operations import Constant, Division from b_asic.core_operations import Constant, Division, Reciprocal
return Division( if isinstance(src, Number):
Constant(src) if isinstance(src, Number) else src, self if src == 1:
) return Reciprocal(self)
else:
return Division(Constant(src), self)
return Division(src, self)
def __lshift__(self, src: SignalSourceProvider) -> Signal: def __lshift__(self, src: SignalSourceProvider) -> Signal:
if self.input_count != 1: if self.input_count != 1:
...@@ -816,10 +839,10 @@ class AbstractOperation(Operation, AbstractGraphComponent): ...@@ -816,10 +839,10 @@ class AbstractOperation(Operation, AbstractGraphComponent):
new_component: Operation = cast( new_component: Operation = cast(
Operation, super().copy_component(*args, **kwargs) Operation, super().copy_component(*args, **kwargs)
) )
for i, inp in enumerate(self.inputs): for i, input in enumerate(self.inputs):
new_component.input(i).latency_offset = inp.latency_offset new_component.input(i).latency_offset = input.latency_offset
for i, outp in enumerate(self.outputs): for i, output in enumerate(self.outputs):
new_component.output(i).latency_offset = outp.latency_offset new_component.output(i).latency_offset = output.latency_offset
new_component.execution_time = self._execution_time new_component.execution_time = self._execution_time
return new_component return new_component
...@@ -911,7 +934,7 @@ class AbstractOperation(Operation, AbstractGraphComponent): ...@@ -911,7 +934,7 @@ class AbstractOperation(Operation, AbstractGraphComponent):
@property @property
def latency(self) -> int: def latency(self) -> int:
if None in [inp.latency_offset for inp in self.inputs] or None in [ if None in [inp.latency_offset for inp in self.inputs] or None in [
outp.latency_offset for outp in self.outputs output.latency_offset for output in self.outputs
]: ]:
raise ValueError( raise ValueError(
"All native offsets have to set to a non-negative value to" "All native offsets have to set to a non-negative value to"
...@@ -921,10 +944,10 @@ class AbstractOperation(Operation, AbstractGraphComponent): ...@@ -921,10 +944,10 @@ class AbstractOperation(Operation, AbstractGraphComponent):
return max( return max(
( (
( (
cast(int, outp.latency_offset) cast(int, output.latency_offset)
- cast(int, inp.latency_offset) - cast(int, input.latency_offset)
) )
for outp, inp in it.product(self.outputs, self.inputs) for output, input in it.product(self.outputs, self.inputs)
) )
) )
...@@ -932,11 +955,11 @@ class AbstractOperation(Operation, AbstractGraphComponent): ...@@ -932,11 +955,11 @@ class AbstractOperation(Operation, AbstractGraphComponent):
def latency_offsets(self) -> Dict[str, Optional[int]]: def latency_offsets(self) -> Dict[str, Optional[int]]:
latency_offsets = {} latency_offsets = {}
for i, inp in enumerate(self.inputs): for i, input in enumerate(self.inputs):
latency_offsets[f"in{i}"] = inp.latency_offset latency_offsets[f"in{i}"] = input.latency_offset
for i, outp in enumerate(self.outputs): for i, output in enumerate(self.outputs):
latency_offsets[f"out{i}"] = outp.latency_offset latency_offsets[f"out{i}"] = output.latency_offset
return latency_offsets return latency_offsets
...@@ -953,17 +976,19 @@ class AbstractOperation(Operation, AbstractGraphComponent): ...@@ -953,17 +976,19 @@ class AbstractOperation(Operation, AbstractGraphComponent):
port_str = port_str.lower() port_str = port_str.lower()
if port_str.startswith("in"): if port_str.startswith("in"):
index_str = port_str[2:] index_str = port_str[2:]
assert index_str.isdigit(), ( if not index_str.isdigit():
"Incorrectly formatted index in string, expected 'in' +" raise ValueError(
f" index, got: {port_str!r}" "Incorrectly formatted index in string, expected 'in'"
) f" + index, got: {port_str!r}"
)
self.input(int(index_str)).latency_offset = latency_offset self.input(int(index_str)).latency_offset = latency_offset
elif port_str.startswith("out"): elif port_str.startswith("out"):
index_str = port_str[3:] index_str = port_str[3:]
assert index_str.isdigit(), ( if not index_str.isdigit():
"Incorrectly formatted index in string, expected 'out' +" raise ValueError(
f" index, got: {port_str!r}" "Incorrectly formatted index in string, expected"
) f" 'out' + index, got: {port_str!r}"
)
self.output(int(index_str)).latency_offset = latency_offset self.output(int(index_str)).latency_offset = latency_offset
else: else:
raise ValueError( raise ValueError(
...@@ -1051,20 +1076,20 @@ class AbstractOperation(Operation, AbstractGraphComponent): ...@@ -1051,20 +1076,20 @@ class AbstractOperation(Operation, AbstractGraphComponent):
def get_io_coordinates( def get_io_coordinates(
self, self,
) -> Tuple[List[List[float]], List[List[float]]]: ) -> Tuple[List[List[float]], List[List[float]]]:
self._check_all_latencies_set()
# Doc-string inherited # Doc-string inherited
input_coords = [ self._check_all_latencies_set()
input_coordinates = [
[ [
self.inputs[k].latency_offset, self.inputs[k].latency_offset,
(1 + 2 * k) / (2 * len(self.inputs)), (1 + 2 * k) / (2 * len(self.inputs)),
] ]
for k in range(len(self.inputs)) for k in range(len(self.inputs))
] ]
output_coords = [ output_coordinates = [
[ [
self.outputs[k].latency_offset, self.outputs[k].latency_offset,
(1 + 2 * k) / (2 * len(self.outputs)), (1 + 2 * k) / (2 * len(self.outputs)),
] ]
for k in range(len(self.outputs)) for k in range(len(self.outputs))
] ]
return input_coords, output_coords return input_coordinates, output_coordinates
...@@ -209,6 +209,9 @@ class InputPort(AbstractPort): ...@@ -209,6 +209,9 @@ class InputPort(AbstractPort):
""" """
if self._source_signal is not None: if self._source_signal is not None:
raise ValueError("Cannot connect already connected input port.") raise ValueError("Cannot connect already connected input port.")
if isinstance(src, Signal):
src.set_destination(self)
return src
# self._source_signal is set by the signal constructor. # self._source_signal is set by the signal constructor.
return Signal(source=src.source, destination=self, name=Name(name)) return Signal(source=src.source, destination=self, name=Name(name))
......
This diff is collapsed.
...@@ -5,7 +5,6 @@ Graphical user interface for B-ASIC scheduler. ...@@ -5,7 +5,6 @@ Graphical user interface for B-ASIC scheduler.
__author__ = "Andreas Bolin" __author__ = "Andreas Bolin"
# __all__ = ['main_window', 'graphics_graph', 'component_item', 'graphics_axes', 'graphics_timeline_item'] # __all__ = ['main_window', 'graphics_graph', 'component_item', 'graphics_axes', 'graphics_timeline_item']
from b_asic.scheduler_gui._version import *
from b_asic.scheduler_gui.axes_item import * from b_asic.scheduler_gui.axes_item import *
from b_asic.scheduler_gui.logger import * from b_asic.scheduler_gui.logger import *
from b_asic.scheduler_gui.main_window import * from b_asic.scheduler_gui.main_window import *
......
...@@ -14,3 +14,10 @@ OPERATION_LATENCY_INACTIVE = QColor(*LATENCY_COLOR) ...@@ -14,3 +14,10 @@ OPERATION_LATENCY_INACTIVE = QColor(*LATENCY_COLOR)
OPERATION_LATENCY_ACTIVE = QColor(0, 207, 181) OPERATION_LATENCY_ACTIVE = QColor(0, 207, 181)
OPERATION_EXECUTION_TIME_INACTIVE = QColor(*EXECUTION_TIME_COLOR) OPERATION_EXECUTION_TIME_INACTIVE = QColor(*EXECUTION_TIME_COLOR)
OPERATION_EXECUTION_TIME_ACTIVE = QColor(*EXECUTION_TIME_COLOR) OPERATION_EXECUTION_TIME_ACTIVE = QColor(*EXECUTION_TIME_COLOR)
OPERATION_HEIGHT = 0.75
OPERATION_GAP = (
1 - OPERATION_HEIGHT
) # TODO: For now, should really fix the bug
SCHEDULE_INDENT = 0.2
__version_info__ = ("0", "1")
__version__ = ".".join(__version_info__)
...@@ -20,11 +20,24 @@ from qtpy.QtWidgets import ( ...@@ -20,11 +20,24 @@ from qtpy.QtWidgets import (
) )
# B-ASIC # B-ASIC
from b_asic.scheduler_gui._preferences import SCHEDULE_INDENT
from b_asic.scheduler_gui.timeline_item import TimelineItem from b_asic.scheduler_gui.timeline_item import TimelineItem
class AxesItem(QGraphicsItemGroup): class AxesItem(QGraphicsItemGroup):
"""A class to represent axes in a graph.""" """
A class to represent axes in a graph.
Parameters
----------
width
height
width_indent
height_indent
width_padding
height_padding
parent
"""
_scale: float = 1.0 _scale: float = 1.0
"""Static, changed from MainWindow.""" """Static, changed from MainWindow."""
...@@ -51,14 +64,14 @@ class AxesItem(QGraphicsItemGroup): ...@@ -51,14 +64,14 @@ class AxesItem(QGraphicsItemGroup):
self, self,
width: int, width: int,
height: int, height: int,
width_indent: float = 0.2, width_indent: float = SCHEDULE_INDENT,
height_indent: float = 0.2, height_indent: float = SCHEDULE_INDENT,
width_padding: float = 0.6, width_padding: float = 0.6,
height_padding: float = 0.5, height_padding: float = 0.5,
parent: Optional[QGraphicsItem] = None, parent: Optional[QGraphicsItem] = None,
): ):
""" """
Constructs a AxesItem. Class for an AxesItem.
*parent* is passed to QGraphicsItemGroup's constructor. *parent* is passed to QGraphicsItemGroup's constructor.
""" """
super().__init__(parent=parent) super().__init__(parent=parent)
...@@ -129,10 +142,10 @@ class AxesItem(QGraphicsItemGroup): ...@@ -129,10 +142,10 @@ class AxesItem(QGraphicsItemGroup):
""" """
return self._height return self._height
# @height.setter @height.setter
# def height(self, height: int) -> None: def height(self, height: int) -> None:
# if self._height != height: if self._height != height:
# self.update_axes(height = height) self.update_axes(height=height)
# @property # @property
# def width_indent(self) -> float: # def width_indent(self) -> float:
...@@ -154,12 +167,13 @@ class AxesItem(QGraphicsItemGroup): ...@@ -154,12 +167,13 @@ class AxesItem(QGraphicsItemGroup):
self._event_items.append(item) self._event_items.append(item)
def set_height(self, height: int) -> "AxesItem": def set_height(self, height: int) -> "AxesItem":
# TODO: implement, docstring # TODO: docstring
if height < 0: if height < 0:
raise ValueError( raise ValueError(
f"'height' greater or equal to 0 expected, got: {height}." f"'height' greater or equal to 0 expected, got: {height}."
) )
raise NotImplementedError self._height = height
self._update_yaxis()
def set_width(self, width: int) -> "AxesItem": def set_width(self, width: int) -> "AxesItem":
# TODO: docstring # TODO: docstring
...@@ -275,6 +289,20 @@ class AxesItem(QGraphicsItemGroup): ...@@ -275,6 +289,20 @@ class AxesItem(QGraphicsItemGroup):
self._x_scale_labels[index + 1].setText(str(index + 1)) self._x_scale_labels[index + 1].setText(str(index + 1))
self._x_scale[index + 1].setX(self._x_scale[index + 1].x() + 1) self._x_scale[index + 1].setX(self._x_scale[index + 1].x() + 1)
def _update_yaxis(self) -> None:
self._y_axis.setLine(
0,
0,
0,
-(
self._height_indent
+ self._height
+ self._height_padding
+ 0.05
),
)
self._y_axis.setPen(self._base_pen)
def _make_base(self) -> None: def _make_base(self) -> None:
# x axis # x axis
self._x_axis.setLine(0, 0, self._width_indent + self._width_padding, 0) self._x_axis.setLine(0, 0, self._width_indent + self._width_padding, 0)
...@@ -322,16 +350,5 @@ class AxesItem(QGraphicsItemGroup): ...@@ -322,16 +350,5 @@ class AxesItem(QGraphicsItemGroup):
) # move timeline ) # move timeline
# y-axis # y-axis
self._y_axis.setLine( self._update_yaxis()
0,
0,
0,
-(
self._height_indent
+ self._height
+ self._height_padding
+ 0.05
),
)
self._y_axis.setPen(self._base_pen)
self.addToGroup(self._y_axis) self.addToGroup(self._y_axis)
...@@ -10,6 +10,7 @@ Start main-window with start_gui(). ...@@ -10,6 +10,7 @@ Start main-window with start_gui().
import inspect import inspect
import os import os
import sys import sys
import webbrowser
from copy import deepcopy from copy import deepcopy
from importlib.machinery import SourceFileLoader from importlib.machinery import SourceFileLoader
from typing import Optional, Union, cast from typing import Optional, Union, cast
...@@ -43,6 +44,7 @@ from qtpy.QtWidgets import ( ...@@ -43,6 +44,7 @@ from qtpy.QtWidgets import (
# B-ASIC # B-ASIC
import b_asic.scheduler_gui.logger as logger import b_asic.scheduler_gui.logger as logger
from b_asic._version import __version__
from b_asic.graph_component import GraphComponent, GraphID from b_asic.graph_component import GraphComponent, GraphID
from b_asic.schedule import Schedule from b_asic.schedule import Schedule
from b_asic.scheduler_gui.axes_item import AxesItem from b_asic.scheduler_gui.axes_item import AxesItem
...@@ -65,9 +67,9 @@ if __debug__: ...@@ -65,9 +67,9 @@ if __debug__:
from qtpy import QtCore from qtpy import QtCore
QT_API = os.environ.get("QT_API", "") QT_API = os.environ.get("QT_API", "")
log.debug("Qt version (runtime): {}".format(QtCore.qVersion())) log.debug("Qt version (runtime): {}".format(QtCore.qVersion()))
log.debug("Qt version (compiletime): {}".format(QtCore.__version__)) log.debug("Qt version (compile time): {}".format(QtCore.__version__))
log.debug("QT_API: {}".format(QT_API)) log.debug("QT_API: {}".format(QT_API))
if QT_API.lower().startswith("pyside"): if QT_API.lower().startswith("pyside"):
import PySide2 import PySide2
...@@ -83,7 +85,9 @@ if __debug__: ...@@ -83,7 +85,9 @@ if __debug__:
QCoreApplication.setOrganizationName("Linköping University") QCoreApplication.setOrganizationName("Linköping University")
QCoreApplication.setOrganizationDomain("liu.se") QCoreApplication.setOrganizationDomain("liu.se")
QCoreApplication.setApplicationName("B-ASIC Scheduler") QCoreApplication.setApplicationName("B-ASIC Scheduler")
# QCoreApplication.setApplicationVersion(__version__) # TODO: read from packet __version__ QCoreApplication.setApplicationVersion(
__version__
) # TODO: read from packet __version__
class MainWindow(QMainWindow, Ui_MainWindow): class MainWindow(QMainWindow, Ui_MainWindow):
...@@ -93,9 +97,10 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -93,9 +97,10 @@ class MainWindow(QMainWindow, Ui_MainWindow):
_schedule: Union[Schedule, None] _schedule: Union[Schedule, None]
_graph: Union[SchedulerItem, None] _graph: Union[SchedulerItem, None]
_scale: float _scale: float
_debug_rects: QGraphicsItemGroup _debug_rectangles: QGraphicsItemGroup
_splitter_pos: int _splitter_pos: int
_splitter_min: int _splitter_min: int
_zoom: float
def __init__(self): def __init__(self):
"""Initialize Scheduler-gui.""" """Initialize Scheduler-gui."""
...@@ -103,7 +108,8 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -103,7 +108,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self._schedule = None self._schedule = None
self._graph = None self._graph = None
self._scale = 75.0 self._scale = 75.0
self._debug_rects = None self._debug_rectangles = None
self._zoom = 1.0
self.setupUi(self) self.setupUi(self)
self._read_settings() self._read_settings()
...@@ -123,8 +129,10 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -123,8 +129,10 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.menu_quit.triggered.connect(self.close) self.menu_quit.triggered.connect(self.close)
self.menu_node_info.triggered.connect(self.show_info_table) self.menu_node_info.triggered.connect(self.show_info_table)
self.menu_exit_dialog.triggered.connect(self.hide_exit_dialog) self.menu_exit_dialog.triggered.connect(self.hide_exit_dialog)
self.actionReorder.triggered.connect(self._actionReorder)
self.actionT.triggered.connect(self._actionTbtn) self.actionT.triggered.connect(self._actionTbtn)
self.splitter.splitterMoved.connect(self._splitter_moved) self.splitter.splitterMoved.connect(self._splitter_moved)
self.actionDocumentation.triggered.connect(self._open_documentation)
# Setup event member functions # Setup event member functions
self.closeEvent = self._close_event self.closeEvent = self._close_event
...@@ -154,7 +162,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -154,7 +162,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
@property @property
def schedule(self) -> Optional[Schedule]: def schedule(self) -> Optional[Schedule]:
"""Get the current schedule.""" """The current schedule."""
return self._schedule return self._schedule
######### #########
...@@ -165,19 +173,42 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -165,19 +173,42 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# TODO: remove # TODO: remove
if self.schedule is None: if self.schedule is None:
return return
self.schedule.plot_schedule() self.schedule.plot()
if self._graph is not None: if self._graph is not None:
print(f"filtersChildEvents(): {self._graph.filtersChildEvents()}") print(f"filtersChildEvents(): {self._graph.filtersChildEvents()}")
# self._printButtonPressed('callback_pushButton()') # self._print_button_pressed('callback_pushButton()')
@Slot()
def _open_documentation(self) -> None:
webbrowser.open_new_tab("https://da.gitlab-pages.liu.se/B-ASIC/")
@Slot()
def _actionReorder(self) -> None:
"""Callback to reorder all operations vertically based on start time.
"""
if self.schedule is None:
return
if self._graph is not None:
self._graph._redraw_from_start()
def wheelEvent(self, event) -> None:
"""Zoom in or out using mouse wheel if control is pressed."""
if event.modifiers() == Qt.KeyboardModifier.ControlModifier:
old_zoom = self._zoom
self._zoom += event.angleDelta().y() / 2500
self.view.scale(self._zoom, self._zoom)
self._zoom = old_zoom
@Slot() @Slot()
def _load_schedule_from_pyfile(self) -> None: def _load_schedule_from_pyfile(self) -> None:
"""SLOT() for SIGNAL(menu_load_from_file.triggered) """
SLOT() for SIGNAL(menu_load_from_file.triggered)
Load a python script as a module and search for a Schedule object. If Load a python script as a module and search for a Schedule object. If
found, opens it.""" found, opens it.
"""
settings = QSettings() settings = QSettings()
last_file = settings.value( last_file = settings.value(
"mainwindow/last_opened_file", "scheduler/last_opened_file",
QStandardPaths.standardLocations(QStandardPaths.HomeLocation)[0], QStandardPaths.standardLocations(QStandardPaths.HomeLocation)[0],
str, str,
) )
...@@ -230,42 +261,48 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -230,42 +261,48 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self, self,
self.tr("File not found"), self.tr("File not found"),
self.tr( self.tr(
"Could not find any Schedule object in file '{}'." "Cannot find any Schedule object in file '{}'."
).format(os.path.basename(abs_path_filename)), ).format(os.path.basename(abs_path_filename)),
) )
log.info( log.info(
"Could not find any Schedule object in file '{}'.".format( "Cannot find any Schedule object in file '{}'.".format(
os.path.basename(abs_path_filename) os.path.basename(abs_path_filename)
) )
) )
del module del module
return return
ret_tuple = QInputDialog.getItem( if len(schedule_obj_list) == 1:
self, schedule = [val for val in schedule_obj_list.values()][0]
self.tr("Load object"), else:
self.tr( ret_tuple = QInputDialog.getItem(
"Found the following Schedule object(s) in file.\n\n" self,
"Select an object to proceed:" self.tr("Load object"),
), self.tr(
schedule_obj_list.keys(), "Found the following Schedule objects in file.\n\n"
0, "Select an object to proceed:"
False, ),
) schedule_obj_list.keys(),
0,
False,
)
if not ret_tuple[1]: # User canceled the operation if not ret_tuple[1]: # User canceled the operation
log.debug("Load schedule operation: user canceled") log.debug("Load schedule operation: user canceled")
del module del module
return return
schedule = schedule_obj_list[ret_tuple[0]]
self.open(schedule_obj_list[ret_tuple[0]]) self.open(schedule)
del module del module
settings.setValue("mainwindow/last_opened_file", abs_path_filename) settings.setValue("scheduler/last_opened_file", abs_path_filename)
@Slot() @Slot()
def close_schedule(self) -> None: def close_schedule(self) -> None:
"""SLOT() for SIGNAL(menu_close_schedule.triggered) """
Closes current schedule.""" SLOT() for SIGNAL(menu_close_schedule.triggered)
Closes current schedule.
"""
if self._graph: if self._graph:
self._graph._signals.component_selected.disconnect( self._graph._signals.component_selected.disconnect(
self.info_table_update_component self.info_table_update_component
...@@ -289,22 +326,26 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -289,22 +326,26 @@ class MainWindow(QMainWindow, Ui_MainWindow):
This method save a schedule. This method save a schedule.
""" """
# TODO: all # TODO: all
self._printButtonPressed("save_schedule()") self._print_button_pressed("save_schedule()")
self.update_statusbar(self.tr("Schedule saved successfully")) self.update_statusbar(self.tr("Schedule saved successfully"))
@Slot() @Slot()
def save_as(self) -> None: def save_as(self) -> None:
"""SLOT() for SIGNAL(menu_save_as.triggered) """
This method save as a schedule.""" SLOT() for SIGNAL(menu_save_as.triggered)
This method save as a schedule.
"""
# TODO: all # TODO: all
self._printButtonPressed("save_schedule()") self._print_button_pressed("save_schedule()")
self.update_statusbar(self.tr("Schedule saved successfully")) self.update_statusbar(self.tr("Schedule saved successfully"))
@Slot(bool) @Slot(bool)
def show_info_table(self, checked: bool) -> None: def show_info_table(self, checked: bool) -> None:
"""SLOT(bool) for SIGNAL(menu_node_info.triggered) """
SLOT(bool) for SIGNAL(menu_node_info.triggered)
Takes in a boolean and hide or show the info table accordingly with Takes in a boolean and hide or show the info table accordingly with
'checked'.""" 'checked'.
"""
# Note: splitter handler index 0 is a hidden splitter handle far most left, use index 1 # Note: splitter handler index 0 is a hidden splitter handle far most left, use index 1
# settings = QSettings() # settings = QSettings()
_, max_ = self.splitter.getRange(1) # tuple(min, max) _, max_ = self.splitter.getRange(1) # tuple(min, max)
...@@ -324,8 +365,8 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -324,8 +365,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
Takes in a boolean and stores 'checked' in 'hide_exit_dialog' item in Takes in a boolean and stores 'checked' in 'hide_exit_dialog' item in
settings. settings.
""" """
s = QSettings() settings = QSettings()
s.setValue("mainwindow/hide_exit_dialog", checked) settings.setValue("scheduler/hide_exit_dialog", checked)
@Slot(int, int) @Slot(int, int)
def _splitter_moved(self, pos: int, index: int) -> None: def _splitter_moved(self, pos: int, index: int) -> None:
...@@ -345,15 +386,15 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -345,15 +386,15 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self._splitter_pos = width self._splitter_pos = width
@Slot(str) @Slot(str)
def info_table_update_component(self, op_id: GraphID) -> None: def info_table_update_component(self, graph_id: GraphID) -> None:
""" """
SLOT(str) for SIGNAL(_graph._signals.component_selected) SLOT(str) for SIGNAL(_graph._signals.component_selected)
Takes in an operator-id, first clears the 'Operator' part of the info Takes in an operator-id, first clears the 'Operator' part of the info
table and then fill in the table with new values from the operator table and then fill in the table with new values from the operator
associated with 'op_id'. associated with *graph_id*.
""" """
self.info_table_clear_component() self.info_table_clear_component()
self._info_table_fill_component(op_id) self._info_table_fill_component(graph_id)
@Slot() @Slot()
def info_table_update_schedule(self) -> None: def info_table_update_schedule(self) -> None:
...@@ -385,8 +426,8 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -385,8 +426,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
in a QCloseEvent and display an exit dialog, depending on in a QCloseEvent and display an exit dialog, depending on
'hide_exit_dialog' in settings. 'hide_exit_dialog' in settings.
""" """
s = QSettings() settings = QSettings()
hide_dialog = s.value("mainwindow/hide_exit_dialog", False, bool) hide_dialog = settings.value("scheduler/hide_exit_dialog", False, bool)
ret = QMessageBox.StandardButton.Yes ret = QMessageBox.StandardButton.Yes
if not hide_dialog: if not hide_dialog:
...@@ -410,7 +451,9 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -410,7 +451,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
if ret == QMessageBox.StandardButton.Yes: if ret == QMessageBox.StandardButton.Yes:
if not hide_dialog: if not hide_dialog:
s.setValue("mainwindow/hide_exit_dialog", checkbox.isChecked()) settings.setValue(
"scheduler/hide_exit_dialog", checkbox.isChecked()
)
self._write_settings() self._write_settings()
log.info("Exit: {}".format(os.path.basename(__file__))) log.info("Exit: {}".format(os.path.basename(__file__)))
event.accept() event.accept()
...@@ -420,7 +463,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -420,7 +463,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
########################### ###########################
# Helper member functions # # Helper member functions #
########################### ###########################
def _printButtonPressed(self, func_name: str) -> None: def _print_button_pressed(self, func_name: str) -> None:
# TODO: remove # TODO: remove
alert = QMessageBox(self) alert = QMessageBox(self)
...@@ -439,6 +482,9 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -439,6 +482,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self._graph._signals.component_selected.connect( self._graph._signals.component_selected.connect(
self.info_table_update_component self.info_table_update_component
) )
self._graph._signals.component_moved.connect(
self.info_table_update_component
)
self._graph._signals.schedule_time_changed.connect( self._graph._signals.schedule_time_changed.connect(
self.info_table_update_schedule self.info_table_update_schedule
) )
...@@ -446,53 +492,63 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -446,53 +492,63 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.update_statusbar(self.tr("Schedule loaded successfully")) self.update_statusbar(self.tr("Schedule loaded successfully"))
def update_statusbar(self, msg: str) -> None: def update_statusbar(self, msg: str) -> None:
"""Take a str and write *msg* to the statusbar with temporarily policy. """
Write *msg* to the statusbar with temporarily policy.
Parameters
----------
msg : str
The message to write.
""" """
self.statusbar.showMessage(msg) self.statusbar.showMessage(msg)
def _write_settings(self) -> None: def _write_settings(self) -> None:
"""Write settings from MainWindow to Settings.""" """Write settings from MainWindow to Settings."""
s = QSettings() settings = QSettings()
s.setValue( settings.setValue(
"mainwindow/maximized", self.isMaximized() "scheduler/maximized", self.isMaximized()
) # window: maximized, in X11 - always False ) # window: maximized, in X11 - always False
s.setValue("mainwindow/pos", self.pos()) # window: pos settings.setValue("scheduler/pos", self.pos()) # window: pos
s.setValue("mainwindow/size", self.size()) # window: size settings.setValue("scheduler/size", self.size()) # window: size
s.setValue( settings.setValue(
"mainwindow/state", self.saveState() "scheduler/state", self.saveState()
) # toolbars, dockwidgets: pos, size ) # toolbars, dockwidgets: pos, size
s.setValue( settings.setValue(
"mainwindow/menu/node_info", self.menu_node_info.isChecked() "scheduler/menu/node_info", self.menu_node_info.isChecked()
)
settings.setValue(
"scheduler/splitter/state", self.splitter.saveState()
) )
s.setValue("mainwindow/splitter/state", self.splitter.saveState()) settings.setValue("scheduler/splitter/pos", self.splitter.sizes()[1])
s.setValue("mainwindow/splitter/pos", self.splitter.sizes()[1])
if s.isWritable(): if settings.isWritable():
log.debug("Settings written to '{}'.".format(s.fileName())) log.debug("Settings written to '{}'.".format(settings.fileName()))
else: else:
log.warning("Settings cant be saved to file, read-only.") log.warning("Settings cant be saved to file, read-only.")
def _read_settings(self) -> None: def _read_settings(self) -> None:
"""Read settings from Settings to MainWindow.""" """Read settings from Settings to MainWindow."""
s = QSettings() settings = QSettings()
if s.value("mainwindow/maximized", defaultValue=False, type=bool): if settings.value(
"scheduler/maximized", defaultValue=False, type=bool
):
self.showMaximized() self.showMaximized()
else: else:
self.move(s.value("mainwindow/pos", self.pos())) self.move(settings.value("scheduler/pos", self.pos()))
self.resize(s.value("mainwindow/size", self.size())) self.resize(settings.value("scheduler/size", self.size()))
self.restoreState(s.value("mainwindow/state", QByteArray())) self.restoreState(settings.value("scheduler/state", QByteArray()))
self.menu_node_info.setChecked( self.menu_node_info.setChecked(
s.value("mainwindow/menu/node_info", True, bool) settings.value("scheduler/menu/node_info", True, bool)
) )
self.splitter.restoreState( self.splitter.restoreState(
s.value("mainwindow/splitter/state", QByteArray()) settings.value("scheduler/splitter/state", QByteArray())
) )
self._splitter_pos = s.value("mainwindow/splitter/pos", 200, int) self._splitter_pos = settings.value("scheduler/splitter/pos", 200, int)
self.menu_exit_dialog.setChecked( self.menu_exit_dialog.setChecked(
s.value("mainwindow/hide_exit_dialog", False, bool) settings.value("scheduler/hide_exit_dialog", False, bool)
) )
log.debug("Settings read from '{}'.".format(s.fileName())) log.debug("Settings read from '{}'.".format(settings.fileName()))
def info_table_fill_schedule(self, schedule: Schedule) -> None: def info_table_fill_schedule(self, schedule: Schedule) -> None:
""" """
...@@ -508,15 +564,15 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -508,15 +564,15 @@ class MainWindow(QMainWindow, Ui_MainWindow):
) )
self.info_table.setItem(2, 1, QTableWidgetItem(str(schedule.cyclic))) self.info_table.setItem(2, 1, QTableWidgetItem(str(schedule.cyclic)))
def _info_table_fill_component(self, op_id: GraphID) -> None: def _info_table_fill_component(self, graph_id: GraphID) -> None:
""" """
Take an operator-id and fill in the 'Operator' part of the info Take an operator-id and fill in the 'Operator' part of the info
table with values from the operator associated with *op_id*. table with values from the operator associated with *graph_id*.
""" """
if self.schedule is None: if self.schedule is None:
return return
op: GraphComponent = cast( op: GraphComponent = cast(
GraphComponent, self.schedule.sfg.find_by_id(op_id) GraphComponent, self.schedule.sfg.find_by_id(graph_id)
) )
si = self.info_table.rowCount() # si = start index si = self.info_table.rowCount() # si = start index
...@@ -537,6 +593,22 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -537,6 +593,22 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.info_table.setItem(si, 1, QTableWidgetItem(str(value))) self.info_table.setItem(si, 1, QTableWidgetItem(str(value)))
si += 1 si += 1
self.info_table.insertRow(si)
self.info_table.setItem(si, 0, QTableWidgetItem("Forward slack"))
self.info_table.setItem(
si, 1, QTableWidgetItem(str(self.schedule.forward_slack(graph_id)))
)
si += 1
self.info_table.insertRow(si)
self.info_table.setItem(si, 0, QTableWidgetItem("Backward slack"))
self.info_table.setItem(
si,
1,
QTableWidgetItem(str(self.schedule.backward_slack(graph_id))),
)
si += 1
def info_table_clear(self) -> None: def info_table_clear(self) -> None:
"""Clears the info table.""" """Clears the info table."""
self.info_table_clear_component() self.info_table_clear_component()
...@@ -555,7 +627,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -555,7 +627,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
"'Operator' not found in info table. It may have been renamed." "'Operator' not found in info table. It may have been renamed."
) )
def exit_app(self): def exit_app(self) -> None:
"""Exit application.""" """Exit application."""
log.info("Exiting the application.") log.info("Exiting the application.")
QApplication.quit() QApplication.quit()
...@@ -573,7 +645,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -573,7 +645,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
) )
def start_gui(): def start_gui() -> None:
app = QApplication(sys.argv) app = QApplication(sys.argv)
window = MainWindow() window = MainWindow()
window.show() window.show()
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="windowIcon"> <property name="windowIcon">
<iconset resource="icons/basic.qrc"> <iconset>
<normaloff>:/icons/basic/small_logo.png</normaloff>:/icons/basic/small_logo.png</iconset> <normaloff>:/icons/basic/small_logo.png</normaloff>:/icons/basic/small_logo.png</iconset>
</property> </property>
<widget class="QWidget" name="centralwidget"> <widget class="QWidget" name="centralwidget">
...@@ -108,6 +108,12 @@ ...@@ -108,6 +108,12 @@
<property name="text"> <property name="text">
<string>Property</string> <string>Property</string>
</property> </property>
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="textAlignment"> <property name="textAlignment">
<set>AlignLeading|AlignVCenter</set> <set>AlignLeading|AlignVCenter</set>
</property> </property>
...@@ -126,7 +132,9 @@ ...@@ -126,7 +132,9 @@
</property> </property>
<property name="font"> <property name="font">
<font> <font>
<bold>true</bold> <weight>50</weight>
<bold>false</bold>
<kerning>true</kerning>
</font> </font>
</property> </property>
<property name="background"> <property name="background">
...@@ -157,7 +165,8 @@ ...@@ -157,7 +165,8 @@
</property> </property>
<property name="font"> <property name="font">
<font> <font>
<bold>true</bold> <weight>50</weight>
<bold>false</bold>
</font> </font>
</property> </property>
<property name="background"> <property name="background">
...@@ -224,10 +233,19 @@ ...@@ -224,10 +233,19 @@
</property> </property>
<addaction name="menu_exit_dialog"/> <addaction name="menu_exit_dialog"/>
</widget> </widget>
<widget class="QMenu" name="menuHelp">
<property name="title">
<string>&amp;Help</string>
</property>
<addaction name="actionDocumentation"/>
<addaction name="separator"/>
<addaction name="actionAbout"/>
</widget>
<addaction name="menuFile"/> <addaction name="menuFile"/>
<addaction name="menu_Edit"/> <addaction name="menu_Edit"/>
<addaction name="menuView"/> <addaction name="menuView"/>
<addaction name="menuWindow"/> <addaction name="menuWindow"/>
<addaction name="menuHelp"/>
</widget> </widget>
<widget class="QStatusBar" name="statusbar"/> <widget class="QStatusBar" name="statusbar"/>
<widget class="QToolBar" name="toolBar"> <widget class="QToolBar" name="toolBar">
...@@ -246,6 +264,7 @@ ...@@ -246,6 +264,7 @@
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="menu_node_info"/> <addaction name="menu_node_info"/>
<addaction name="actionT"/> <addaction name="actionT"/>
<addaction name="actionReorder"/>
</widget> </widget>
<action name="menu_load_from_file"> <action name="menu_load_from_file">
<property name="icon"> <property name="icon">
...@@ -291,7 +310,7 @@ ...@@ -291,7 +310,7 @@
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="icon"> <property name="icon">
<iconset resource="icons/misc.qrc"> <iconset>
<normaloff>:/icons/misc/right_panel.svg</normaloff> <normaloff>:/icons/misc/right_panel.svg</normaloff>
<normalon>:/icons/misc/right_filled_panel.svg</normalon>:/icons/misc/right_panel.svg</iconset> <normalon>:/icons/misc/right_filled_panel.svg</normalon>:/icons/misc/right_panel.svg</iconset>
</property> </property>
...@@ -364,6 +383,25 @@ ...@@ -364,6 +383,25 @@
<string>&amp;Close Schedule</string> <string>&amp;Close Schedule</string>
</property> </property>
</action> </action>
<action name="actionAbout">
<property name="text">
<string>About</string>
</property>
</action>
<action name="actionDocumentation">
<property name="text">
<string>Documentation</string>
</property>
</action>
<action name="actionReorder">
<property name="text">
<string>Reorder</string>
</property>
<property name="toolTip">
<string>Reorder schedule based on start time</string>
</property>
</action>
</widget> </widget>
<resources/>
<connections/> <connections/>
</ui> </ui>
...@@ -30,7 +30,15 @@ from b_asic.scheduler_gui._preferences import ( ...@@ -30,7 +30,15 @@ from b_asic.scheduler_gui._preferences import (
class OperationItem(QGraphicsItemGroup): class OperationItem(QGraphicsItemGroup):
"""Class to represent a component in a graph.""" """
Class to represent an operation in a graph.
Parameters
----------
operation : Operation
height : float, default: 1.0
parent : QGraphicsItem, optional
"""
_scale: float = 1.0 _scale: float = 1.0
"""Static, changed from MainWindow.""" """Static, changed from MainWindow."""
...@@ -48,7 +56,7 @@ class OperationItem(QGraphicsItemGroup): ...@@ -48,7 +56,7 @@ class OperationItem(QGraphicsItemGroup):
def __init__( def __init__(
self, self,
operation: Operation, operation: Operation,
height: float = 0.75, height: float = 1.0,
parent: Optional[QGraphicsItem] = None, parent: Optional[QGraphicsItem] = None,
): ):
""" """
...@@ -90,13 +98,13 @@ class OperationItem(QGraphicsItemGroup): ...@@ -90,13 +98,13 @@ class OperationItem(QGraphicsItemGroup):
del item del item
@property @property
def op_id(self) -> GraphID: def graph_id(self) -> GraphID:
"""Get the op-id.""" """The graph-id of the operation that the item corresponds to."""
return self._operation.graph_id return self._operation.graph_id
@property @property
def operation(self) -> Operation: def operation(self) -> Operation:
"""Get the operation.""" """The operation that the item corresponds to."""
return self._operation return self._operation
@property @property
...@@ -116,26 +124,40 @@ class OperationItem(QGraphicsItemGroup): ...@@ -116,26 +124,40 @@ class OperationItem(QGraphicsItemGroup):
@property @property
def end_time(self) -> int: def end_time(self) -> int:
"""Get the relative end time.""" """The relative end time."""
return self._end_time return self._end_time
@property @property
def event_items(self) -> List[QGraphicsItem]: def event_items(self) -> List[QGraphicsItem]:
"""Returns a list of objects, that receives events.""" """List of objects that receives events."""
return [self] return [self]
def get_port_location(self, key) -> QPointF: def get_port_location(self, key: str) -> QPointF:
"""
Return the location specified by *key*.
Parameters
----------
key : str
The port key.
Returns
-------
The location as a QPointF.
"""
return self.mapToParent(self._ports[key]["pos"]) return self.mapToParent(self._ports[key]["pos"])
def set_active(self): def set_active(self) -> None:
"""Set the item as active, i.e., draw it in special colors."""
self._set_background(OPERATION_LATENCY_ACTIVE) self._set_background(OPERATION_LATENCY_ACTIVE)
self.setCursor(QCursor(Qt.CursorShape.ClosedHandCursor)) self.setCursor(QCursor(Qt.CursorShape.ClosedHandCursor))
def set_inactive(self): def set_inactive(self) -> None:
"""Set the item as inactive, i.e., draw it in standard colors."""
self._set_background(OPERATION_LATENCY_INACTIVE) self._set_background(OPERATION_LATENCY_INACTIVE)
self.setCursor(QCursor(Qt.CursorShape.OpenHandCursor)) self.setCursor(QCursor(Qt.CursorShape.OpenHandCursor))
def _set_background(self, color: QColor): def _set_background(self, color: QColor) -> None:
brush = QBrush(color) brush = QBrush(color)
self._latency_item.setBrush(brush) self._latency_item.setBrush(brush)
...@@ -198,13 +220,13 @@ class OperationItem(QGraphicsItemGroup): ...@@ -198,13 +220,13 @@ class OperationItem(QGraphicsItemGroup):
key = f"{prefix}{i}" key = f"{prefix}{i}"
self._ports[key]["pos"] = pos self._ports[key]["pos"] = pos
port_pos = self.mapToParent(pos) port_pos = self.mapToParent(pos)
port = QGraphicsEllipseItem( new_port = QGraphicsEllipseItem(
-port_size / 2, -port_size / 2, port_size, port_size -port_size / 2, -port_size / 2, port_size, port_size
) )
port.setPen(port_outline_pen) new_port.setPen(port_outline_pen)
port.setBrush(port_filling_brush) new_port.setBrush(port_filling_brush)
port.setPos(port_pos.x(), port_pos.y()) new_port.setPos(port_pos.x(), port_pos.y())
self._port_items.append(port) self._port_items.append(new_port)
create_ports(inputs, "in") create_ports(inputs, "in")
create_ports(outputs, "out") create_ports(outputs, "out")
......
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""B-ASIC Scheduler-gui Graphics Graph Event Module. """
B-ASIC Scheduler-gui Graphics Graph Event Module.
Contains the scheduler-gui SchedulerEvent class containing event filters and handlers for SchedulerItem objects. Contains the scheduler-gui SchedulerEvent class containing event filters and handlers for SchedulerItem objects.
""" """
...@@ -20,19 +21,28 @@ from qtpy.QtWidgets import ( ...@@ -20,19 +21,28 @@ from qtpy.QtWidgets import (
) )
from b_asic.schedule import Schedule 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.axes_item import AxesItem
from b_asic.scheduler_gui.operation_item import OperationItem from b_asic.scheduler_gui.operation_item import OperationItem
from b_asic.scheduler_gui.timeline_item import TimelineItem from b_asic.scheduler_gui.timeline_item import TimelineItem
class SchedulerEvent: # PyQt5 class SchedulerEvent: # PyQt5
"""Event filter and handlers for SchedulerItem""" """
Event filter and handlers for SchedulerItem.
Parameters
----------
parent : QGraphicsItem, optional
The parent QGraphicsItem.
"""
class Signals(QObject): # PyQt5 class Signals(QObject): # PyQt5
"""A class representing signals.""" """A class representing signals."""
component_selected = Signal(str) component_selected = Signal(str)
schedule_time_changed = Signal() schedule_time_changed = Signal()
component_moved = Signal(str)
_axes: Optional[AxesItem] _axes: Optional[AxesItem]
_current_pos: QPointF _current_pos: QPointF
...@@ -59,9 +69,9 @@ class SchedulerEvent: # PyQt5 ...@@ -59,9 +69,9 @@ class SchedulerEvent: # PyQt5
def set_item_inactive(self, item: OperationItem) -> None: def set_item_inactive(self, item: OperationItem) -> None:
raise NotImplementedError raise NotImplementedError
################# ###########
#### Filters #### # Filters #
################# ###########
@overload @overload
def installSceneEventFilters(self, filterItems: QGraphicsItem) -> None: def installSceneEventFilters(self, filterItems: QGraphicsItem) -> None:
... ...
...@@ -93,8 +103,10 @@ class SchedulerEvent: # PyQt5 ...@@ -93,8 +103,10 @@ class SchedulerEvent: # PyQt5
... ...
def removeSceneEventFilters(self, filterItems) -> None: def removeSceneEventFilters(self, filterItems) -> None:
"""Removes an event filter on 'filterItems' from 'self'. 'filterItems' can """
be one object or a list of objects.""" Removes an event filter on *filterItems* from *self*. *filterItems* can
be one object or a list of objects.
"""
item: OperationItem item: OperationItem
for item in filterItems: for item in filterItems:
item.removeSceneEventFilter(self) item.removeSceneEventFilter(self)
...@@ -105,24 +117,23 @@ class SchedulerEvent: # PyQt5 ...@@ -105,24 +117,23 @@ class SchedulerEvent: # PyQt5
If False is returned, the event is forwarded to the appropriate child in If False is returned, the event is forwarded to the appropriate child in
the event chain. the event chain.
""" """
handler = None
if isinstance(item, OperationItem): # one component if isinstance(item, OperationItem): # one component
switch = { switch = {
# QEvent.FocusIn: self.comp_focusInEvent, # QEvent.FocusIn: self.operation_focusInEvent,
# QEvent.GraphicsSceneContextMenu: self.comp_contextMenuEvent, # QEvent.GraphicsSceneContextMenu: self.operation_contextMenuEvent,
# QEvent.GraphicsSceneDragEnter: self.comp_dragEnterEvent, # QEvent.GraphicsSceneDragEnter: self.operation_dragEnterEvent,
# QEvent.GraphicsSceneDragMove: self.comp_dragMoveEvent, # QEvent.GraphicsSceneDragMove: self.operation_dragMoveEvent,
# QEvent.GraphicsSceneDragLeave: self.comp_dragLeaveEvent, # QEvent.GraphicsSceneDragLeave: self.operation_dragLeaveEvent,
# QEvent.GraphicsSceneDrop: self.comp_dropEvent, # QEvent.GraphicsSceneDrop: self.operation_dropEvent,
# QEvent.GraphicsSceneHoverEnter: self.comp_hoverEnterEvent, # QEvent.GraphicsSceneHoverEnter: self.operation_hoverEnterEvent,
# QEvent.GraphicsSceneHoverMove: self.comp_hoverMoveEvent, # QEvent.GraphicsSceneHoverMove: self.operation_hoverMoveEvent,
# QEvent.GraphicsSceneHoverLeave: self.comp_hoverLeaveEvent, # QEvent.GraphicsSceneHoverLeave: self.operation_hoverLeaveEvent,
QEvent.GraphicsSceneMouseMove: self.comp_mouseMoveEvent, QEvent.GraphicsSceneMouseMove: self.operation_mouseMoveEvent,
QEvent.GraphicsSceneMousePress: self.comp_mousePressEvent, QEvent.GraphicsSceneMousePress: self.operation_mousePressEvent,
QEvent.GraphicsSceneMouseRelease: self.comp_mouseReleaseEvent, QEvent.GraphicsSceneMouseRelease: self.operation_mouseReleaseEvent,
# QEvent.GraphicsSceneMouseDoubleClick: self.comp_mouseDoubleClickEvent, # QEvent.GraphicsSceneMouseDoubleClick: self.operation_mouseDoubleClickEvent,
# QEvent.GraphicsSceneWheel: self.comp_wheelEvent # QEvent.GraphicsSceneWheel: self.operation_wheelEvent
} }
handler = switch.get(event.type()) handler = switch.get(event.type())
...@@ -145,109 +156,134 @@ class SchedulerEvent: # PyQt5 ...@@ -145,109 +156,134 @@ class SchedulerEvent: # PyQt5
if handler is not None: if handler is not None:
handler(event) handler(event)
return True return True
return False # returns False if event is ignored and pass through event to its child return False
# def sceneEvent(self, event: QEvent) -> bool: #################################
# print(f'sceneEvent() --> {event.type()}') # Event Handlers: OperationItem #
# # event.accept() #################################
# # QApplication.sendEvent(self.scene(), event) def operation_focusInEvent(self, event: QFocusEvent) -> None:
# return False
###############################################
#### Event Handlers: OperationItem ####
###############################################
def comp_focusInEvent(self, event: QFocusEvent) -> None:
... ...
def comp_contextMenuEvent( def operation_contextMenuEvent(
self, event: QGraphicsSceneContextMenuEvent self, event: QGraphicsSceneContextMenuEvent
) -> None: ) -> None:
... ...
def comp_dragEnterEvent(self, event: QGraphicsSceneDragDropEvent) -> None: def operation_dragEnterEvent(
self, event: QGraphicsSceneDragDropEvent
) -> None:
... ...
def comp_dragMoveEvent(self, event: QGraphicsSceneDragDropEvent) -> None: def operation_dragMoveEvent(
self, event: QGraphicsSceneDragDropEvent
) -> None:
... ...
def comp_dragLeaveEvent(self, event: QGraphicsSceneDragDropEvent) -> None: def operation_dragLeaveEvent(
self, event: QGraphicsSceneDragDropEvent
) -> None:
... ...
def comp_dropEvent(self, event: QGraphicsSceneDragDropEvent) -> None: def operation_dropEvent(self, event: QGraphicsSceneDragDropEvent) -> None:
... ...
def comp_hoverEnterEvent(self, event: QGraphicsSceneHoverEvent) -> None: def operation_hoverEnterEvent(
self, event: QGraphicsSceneHoverEvent
) -> None:
... ...
def comp_hoverMoveEvent(self, event: QGraphicsSceneHoverEvent) -> None: def operation_hoverMoveEvent(
self, event: QGraphicsSceneHoverEvent
) -> None:
... ...
def comp_hoverLeaveEvent(self, event: QGraphicsSceneHoverEvent) -> None: def operation_hoverLeaveEvent(
self, event: QGraphicsSceneHoverEvent
) -> None:
... ...
def comp_mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None: def operation_mouseMoveEvent(
self, event: QGraphicsSceneMouseEvent
) -> None:
""" """
Set the position of the graphical element in the graphic scene, Set the position of the graphical element in the graphic scene,
translate coordinates of the cursor within the graphic element in the translate coordinates of the cursor within the graphic element in the
coordinate system of the parent object. The object can only move coordinate system of the parent object.
horizontally in x-axis scale steps.
""" """
def update_pos(item, dx): def update_pos(operation_item, dx, dy):
pos = item.x() + dx pos_x = operation_item.x() + dx
if self.is_component_valid_pos(item, pos): pos_y = operation_item.y() + dy * (
item.setX(pos) OPERATION_GAP + OPERATION_HEIGHT
)
if self.is_component_valid_pos(operation_item, pos_x):
operation_item.setX(pos_x)
operation_item.setY(pos_y)
self._current_pos.setX(self._current_pos.x() + dx) self._current_pos.setX(self._current_pos.x() + dx)
self._redraw_lines(item) self._current_pos.setY(self._current_pos.y() + dy)
self._redraw_lines(operation_item)
self._schedule._y_locations[
operation_item.operation.graph_id
] += dy
item: OperationItem = self.scene().mouseGrabberItem() item: OperationItem = self.scene().mouseGrabberItem()
delta_x = (item.mapToParent(event.pos()) - self._current_pos).x() delta_x = (item.mapToParent(event.pos()) - self._current_pos).x()
delta_y = (item.mapToParent(event.pos()) - self._current_pos).y()
delta_y_steps = round(delta_y / (OPERATION_GAP + OPERATION_HEIGHT))
if delta_x > 0.505: if delta_x > 0.505:
update_pos(item, 1) update_pos(item, 1, delta_y_steps)
elif delta_x < -0.505: elif delta_x < -0.505:
update_pos(item, -1) update_pos(item, -1, delta_y_steps)
elif delta_y_steps != 0:
update_pos(item, 0, delta_y_steps)
def comp_mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None: def operation_mousePressEvent(
self, event: QGraphicsSceneMouseEvent
) -> None:
""" """
Changes the cursor to ClosedHandCursor when grabbing an object and Changes the cursor to ClosedHandCursor when grabbing an object and
stores the current position in item's parent coordinates. 'event' will stores the current position in item's parent coordinates. *event* will
by default be accepted, and this item is then the mouse grabber. This by default be accepted, and this item is then the mouse grabber. This
allows the item to receive future move, release and double-click events. allows the item to receive future move, release and double-click events.
""" """
item: OperationItem = self.scene().mouseGrabberItem() item: OperationItem = self.scene().mouseGrabberItem()
self._signals.component_selected.emit(item.op_id) self._signals.component_selected.emit(item.graph_id)
self._current_pos = item.mapToParent(event.pos()) self._current_pos = item.mapToParent(event.pos())
self.set_item_active(item) self.set_item_active(item)
event.accept() event.accept()
def comp_mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None: def operation_mouseReleaseEvent(
self, event: QGraphicsSceneMouseEvent
) -> None:
"""Change the cursor to OpenHandCursor when releasing an object.""" """Change the cursor to OpenHandCursor when releasing an object."""
item: OperationItem = self.scene().mouseGrabberItem() item: OperationItem = self.scene().mouseGrabberItem()
self.set_item_inactive(item) self.set_item_inactive(item)
self.set_new_starttime(item) self.set_new_start_time(item)
pos = item.x() pos_x = item.x()
redraw = False redraw = False
if pos < 0: if pos_x < 0:
pos += self._schedule.schedule_time pos_x += self._schedule.schedule_time
redraw = True redraw = True
if pos > self._schedule.schedule_time: if pos_x > self._schedule.schedule_time:
pos = pos % self._schedule.schedule_time pos_x = pos_x % self._schedule.schedule_time
redraw = True redraw = True
if redraw: if redraw:
item.setX(pos) item.setX(pos_x)
self._redraw_lines(item) self._redraw_lines(item)
self._signals.component_moved.emit(item.graph_id)
def comp_mouseDoubleClickEvent( def operation_mouseDoubleClickEvent(
self, event: QGraphicsSceneMouseEvent self, event: QGraphicsSceneMouseEvent
) -> None: ) -> None:
... ...
def comp_wheelEvent(self, event: QGraphicsSceneWheelEvent) -> None: def operation_wheelEvent(self, event: QGraphicsSceneWheelEvent) -> None:
... ...
############################################### ###################################
#### Event Handlers: GraphicsLineTem #### # Event Handlers: GraphicsLineTem #
############################################### ###################################
def timeline_mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None: def timeline_mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None:
""" """
Set the position of the graphical element in the graphic scene, Set the position of the graphical element in the graphic scene,
...@@ -256,13 +292,13 @@ class SchedulerEvent: # PyQt5 ...@@ -256,13 +292,13 @@ class SchedulerEvent: # PyQt5
horizontally in x-axis scale steps. horizontally in x-axis scale steps.
""" """
def update_pos(item, dx): def update_pos(timeline_item, dx):
pos = item.x() + dx pos = timeline_item.x() + dx
if self.is_valid_delta_time(self._delta_time + dx): if self.is_valid_delta_time(self._delta_time + dx):
item.setX(pos) timeline_item.setX(pos)
self._current_pos.setX(self._current_pos.x() + dx) self._current_pos.setX(self._current_pos.x() + dx)
self._delta_time += dx self._delta_time += dx
item.set_text(self._delta_time) timeline_item.set_text(self._delta_time)
item: TimelineItem = self.scene().mouseGrabberItem() item: TimelineItem = self.scene().mouseGrabberItem()
delta_x = (item.mapToParent(event.pos()) - self._current_pos).x() delta_x = (item.mapToParent(event.pos()) - self._current_pos).x()
...@@ -274,7 +310,8 @@ class SchedulerEvent: # PyQt5 ...@@ -274,7 +310,8 @@ class SchedulerEvent: # PyQt5
def timeline_mousePressEvent( def timeline_mousePressEvent(
self, event: QGraphicsSceneMouseEvent self, event: QGraphicsSceneMouseEvent
) -> None: ) -> None:
"""Store the current position in item's parent coordinates. 'event' will """
Store the current position in item's parent coordinates. *event* will
by default be accepted, and this item is then the mouse grabber. This by default be accepted, and this item is then the mouse grabber. This
allows the item to receive future move, release and double-click events. allows the item to receive future move, release and double-click events.
""" """
......
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""B-ASIC Scheduler-gui Graphics Graph Item Module. """
B-ASIC Scheduler-gui Graphics Graph Item Module.
Contains the scheduler-gui SchedulerItem class for drawing and Contains the scheduler-gui SchedulerItem class for drawing and
maintain a component in a graph. maintain a component in a graph.
...@@ -17,6 +18,11 @@ from qtpy.QtWidgets import QGraphicsItem, QGraphicsItemGroup ...@@ -17,6 +18,11 @@ from qtpy.QtWidgets import QGraphicsItem, QGraphicsItemGroup
from b_asic.operation import Operation from b_asic.operation import Operation
from b_asic.port import InputPort from b_asic.port import InputPort
from b_asic.schedule import Schedule from b_asic.schedule import Schedule
from b_asic.scheduler_gui._preferences import (
OPERATION_GAP,
OPERATION_HEIGHT,
SCHEDULE_INDENT,
)
from b_asic.scheduler_gui.axes_item import AxesItem from b_asic.scheduler_gui.axes_item import AxesItem
from b_asic.scheduler_gui.operation_item import OperationItem from b_asic.scheduler_gui.operation_item import OperationItem
from b_asic.scheduler_gui.scheduler_event import SchedulerEvent from b_asic.scheduler_gui.scheduler_event import SchedulerEvent
...@@ -30,11 +36,19 @@ class SchedulerItem(SchedulerEvent, QGraphicsItemGroup): # PySide2 / PyQt5 ...@@ -30,11 +36,19 @@ class SchedulerItem(SchedulerEvent, QGraphicsItemGroup): # PySide2 / PyQt5
AxesItem, as well as components from OperationItem. It AxesItem, as well as components from OperationItem. It
also inherits from SchedulerEvent, which acts as a filter for events also inherits from SchedulerEvent, which acts as a filter for events
to OperationItem objects. to OperationItem objects.
Parameters
==========
schedule : Schedule
The Schedule to draw.
parent : QGraphicsItem, optional
The parent. Passed to the constructor of QGraphicsItemGroup
""" """
_axes: Optional[AxesItem] _axes: Optional[AxesItem]
_components: List[OperationItem] _operation_items: Dict[str, OperationItem]
_components_height: float
_x_axis_indent: float _x_axis_indent: float
_event_items: List[QGraphicsItem] _event_items: List[QGraphicsItem]
_signal_dict: Dict[OperationItem, Set[SignalItem]] _signal_dict: Dict[OperationItem, Set[SignalItem]]
...@@ -42,7 +56,8 @@ class SchedulerItem(SchedulerEvent, QGraphicsItemGroup): # PySide2 / PyQt5 ...@@ -42,7 +56,8 @@ class SchedulerItem(SchedulerEvent, QGraphicsItemGroup): # PySide2 / PyQt5
def __init__( def __init__(
self, schedule: Schedule, parent: Optional[QGraphicsItem] = None self, schedule: Schedule, parent: Optional[QGraphicsItem] = None
): ):
"""Constructs a SchedulerItem. 'parent' is passed to QGraphicsItemGroup's constructor. """
Construct a SchedulerItem. *parent* is passed to QGraphicsItemGroup's constructor.
""" """
# QGraphicsItemGroup.__init__(self, self) # QGraphicsItemGroup.__init__(self, self)
# SchedulerEvent.__init__(self) # SchedulerEvent.__init__(self)
...@@ -53,15 +68,15 @@ class SchedulerItem(SchedulerEvent, QGraphicsItemGroup): # PySide2 / PyQt5 ...@@ -53,15 +68,15 @@ class SchedulerItem(SchedulerEvent, QGraphicsItemGroup): # PySide2 / PyQt5
# super().__init__(parent=self) # super().__init__(parent=self)
self._schedule = schedule self._schedule = schedule
self._axes = None self._axes = None
self._components = [] self._operation_items = {}
self._components_height = 0.0 self._x_axis_indent = SCHEDULE_INDENT
self._x_axis_indent = 0.2
self._event_items = [] self._event_items = []
self._signal_dict = defaultdict(set) self._signal_dict = defaultdict(set)
self._make_graph() self._make_graph()
def clear(self) -> None: def clear(self) -> None:
"""Sets all children's parent to 'None' and delete the children objects. """
Set all children's parent to 'None' and delete the children objects.
""" """
self._event_items = [] self._event_items = []
for item in self.childItems(): for item in self.childItems():
...@@ -69,14 +84,26 @@ class SchedulerItem(SchedulerEvent, QGraphicsItemGroup): # PySide2 / PyQt5 ...@@ -69,14 +84,26 @@ class SchedulerItem(SchedulerEvent, QGraphicsItemGroup): # PySide2 / PyQt5
del item del item
def is_component_valid_pos(self, item: OperationItem, pos: float) -> bool: 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.""" Take in a component position and return True if the component's new
position is valid, False otherwise.
Parameters
==========
item : OperationItem
The component.
pos : float
The x-position to check.
"""
# TODO: implement # TODO: implement
assert self.schedule is not None, "No schedule installed." if self.schedule is None:
raise ValueError("No schedule installed.")
end_time = item.end_time end_time = item.end_time
new_start_time = floor(pos) - floor(self._x_axis_indent) new_start_time = floor(pos) - floor(self._x_axis_indent)
slacks = self.schedule.slacks(item.op_id) slacks = self.schedule.slacks(item.graph_id)
op_start_time = self.schedule.start_time_of_operation(item.op_id) op_start_time = self.schedule.start_time_of_operation(item.graph_id)
if not -slacks[0] <= new_start_time - op_start_time <= slacks[1]: if not -slacks[0] <= new_start_time - op_start_time <= slacks[1]:
# Cannot move due to dependencies # Cannot move due to dependencies
return False return False
...@@ -95,54 +122,81 @@ class SchedulerItem(SchedulerEvent, QGraphicsItemGroup): # PySide2 / PyQt5 ...@@ -95,54 +122,81 @@ class SchedulerItem(SchedulerEvent, QGraphicsItemGroup): # PySide2 / PyQt5
return True return True
def _redraw_lines(self, item: OperationItem): 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:
"""Update lines connected to *item*.""" """Update lines connected to *item*."""
for signal in self._signal_dict[item]: for signal in self._signal_dict[item]:
signal.update_path() signal.update_path()
def set_item_active(self, item: OperationItem): 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() item.set_active()
for signal in self._signal_dict[item]: for signal in self._signal_dict[item]:
signal.set_active() signal.set_active()
def set_item_inactive(self, item: OperationItem): 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() item.set_inactive()
for signal in self._signal_dict[item]: for signal in self._signal_dict[item]:
signal.set_inactive() signal.set_inactive()
def set_new_starttime(self, item: OperationItem) -> None: def set_new_start_time(self, item: OperationItem) -> None:
"""Set new starttime for *item*.""" """Set new start time for *item*."""
pos = item.x() pos = item.x()
op_start_time = self.schedule.start_time_of_operation(item.op_id) op_start_time = self.schedule.start_time_of_operation(item.graph_id)
new_start_time = floor(pos) - floor(self._x_axis_indent) new_start_time = floor(pos) - floor(self._x_axis_indent)
move_time = new_start_time - op_start_time move_time = new_start_time - op_start_time
if move_time: if move_time:
self.schedule.move_operation(item.op_id, move_time) self.schedule.move_operation(item.graph_id, move_time)
def is_valid_delta_time(self, delta_time: int) -> bool: def is_valid_delta_time(self, delta_time: int) -> bool:
"""Takes in a delta time and returns true if the new schedule time is """
valid, false otherwise.""" Takes in a delta time and returns True if the schedule time can be changed by *delta_time*. False otherwise.
"""
# TODO: implement # TODO: implement
# item = self.scene().mouseGrabberItem() # item = self.scene().mouseGrabberItem()
assert self.schedule is not None, "No schedule installed." if self.schedule is None:
raise ValueError("No schedule installed.")
return ( return (
self.schedule.schedule_time + delta_time self.schedule.schedule_time + delta_time
>= self.schedule.get_max_end_time() >= self.schedule.get_max_end_time()
) )
def set_schedule_time(self, delta_time: int) -> None: def set_schedule_time(self, delta_time: int) -> None:
"""Set the schedule time and redraw the graph.""" """Change the schedule time by *delta_time* and redraw the graph."""
if self._axes is None: if self._axes is None:
raise RuntimeError("No AxesItem!") raise RuntimeError("No AxesItem!")
assert self.schedule is not None, "No schedule installed." if self.schedule is None:
raise ValueError("No schedule installed.")
self.schedule.set_schedule_time( self.schedule.set_schedule_time(
self.schedule.schedule_time + delta_time self.schedule.schedule_time + delta_time
) )
self._axes.set_width(self._axes.width + delta_time) self._axes.set_width(self._axes.width + delta_time)
# Redraw all lines # Redraw all lines
for signals in self._signal_dict.values(): self._redraw_all_lines()
for signal in signals:
signal.update_path()
@property @property
def schedule(self) -> Schedule: def schedule(self) -> Schedule:
...@@ -155,62 +209,84 @@ class SchedulerItem(SchedulerEvent, QGraphicsItemGroup): # PySide2 / PyQt5 ...@@ -155,62 +209,84 @@ class SchedulerItem(SchedulerEvent, QGraphicsItemGroup): # PySide2 / PyQt5
@property @property
def components(self) -> List[OperationItem]: def components(self) -> List[OperationItem]:
return self._components return list(component for component in self._operation_items.values())
@property @property
def event_items(self) -> List[QGraphicsItem]: def event_items(self) -> List[QGraphicsItem]:
"""Return a list of objects that receives events.""" """Return a list of objects that receives events."""
return self._event_items return self._event_items
def _make_graph(self) -> None: def _set_position(self, graph_id) -> None:
"""Makes a new graph out of the stored attributes.""" op_item = self._operation_items[graph_id]
# build components op_item.setPos(
spacing = 0.2 self._x_axis_indent + self.schedule.start_times[graph_id],
_components_dict = {} self.schedule._get_y_position(
# print('Start times:') graph_id, OPERATION_HEIGHT, OPERATION_GAP
for op_id, op_start_time in self.schedule.start_times.items(): ),
operation = cast(Operation, self.schedule.sfg.find_by_id(op_id)) )
# if not isinstance(op, (Input, Output)): def _redraw_from_start(self) -> None:
self._components_height += spacing self.schedule._reset_y_locations()
component = OperationItem(operation, parent=self) for graph_id in {
component.setPos( k: v
self._x_axis_indent + op_start_time, self._components_height for k, v in sorted(
self.schedule.start_times.items(), key=lambda item: item[1]
) )
self._components.append(component) }:
_components_dict[operation] = component self._set_position(graph_id)
self._components_height += component.height self._redraw_all_lines()
self._event_items += component.event_items
# self._components_height += spacing
def _update_axes(self, build=False) -> None:
# build axes # build axes
schedule_time = self.schedule.schedule_time schedule_time = self.schedule.schedule_time
self._axes = AxesItem( max_pos_graph_id = max(
schedule_time, int(self._components_height - spacing) self.schedule._y_locations, key=self.schedule._y_locations.get
) )
self._axes.setPos(0, self._components_height + spacing * 2) y_pos_min = self.schedule._get_y_position(
self._event_items += self._axes.event_items max_pos_graph_id, OPERATION_HEIGHT, OPERATION_GAP
)
if self._axes is None or build:
self._axes = AxesItem(schedule_time, y_pos_min + 0.5)
self._event_items += self._axes.event_items
else:
self._axes.set_height(y_pos_min + 0.5)
self._axes.setPos(0, y_pos_min + OPERATION_HEIGHT + OPERATION_GAP)
def _make_graph(self) -> None:
"""Make 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
# self._axes.width = schedule_time # self._axes.width = schedule_time
# add axes and components # add axes and components
self._update_axes(build=True)
self.addToGroup(self._axes) self.addToGroup(self._axes)
# self._axes.update_axes(schedule_time - 2, self._components_height, self._x_axis_indent) for component in self.components:
for component in self._components:
self.addToGroup(component) self.addToGroup(component)
# self.addToGroup(self._components)
# add signals # add signals
for component in self._components: for component in self.components:
for output_port in component.operation.outputs: for output_port in component.operation.outputs:
for signal in output_port.signals: for signal in output_port.signals:
destination = cast(InputPort, signal.destination) destination = cast(InputPort, signal.destination)
dest_component = _components_dict[destination.operation] destination_component = self._operation_items[
destination.operation.graph_id
]
gui_signal = SignalItem( gui_signal = SignalItem(
component, dest_component, signal, parent=self component, destination_component, signal, parent=self
) )
self.addToGroup(gui_signal) self.addToGroup(gui_signal)
self._signal_dict[component].add(gui_signal) self._signal_dict[component].add(gui_signal)
self._signal_dict[dest_component].add(gui_signal) self._signal_dict[destination_component].add(gui_signal)
pprint(SchedulerItem.__mro__) pprint(SchedulerItem.__mro__)